Swift

정규 표현식 (Regex)

GREEN.1229 2022. 9. 13. 12:23

안녕하세요. 그린입니다🍏

이번 포스팅에서는 정규 표현식에 대해 학습해보겠습니다🙌

 

정규 표현식이란?

우선 정규 표현식을 Swift에서 사용하는것을 알아보기에 앞서 정규 표현식이 무엇인지 그 정의부터 깨우치고 가는게 맞을것 같아요.

그렇기에 정규 표현식의 가장 기본적인 개념을 찾아봤습니다.

(이런 개념적인것을 파볼때는 역시 위키 백과가 짱인것 같아요..!)

정규 표현식은 영어로 regular expression이라고 불리며 우리는 줄여서 Regex라고 부릅니다.

정규식은 주로 특정한 규칙을 가진 문자열의 집합을 표현하는데 사용되는 형식 언어입니다.

여기서 형식 언어란 수학, 컴퓨터 과학, 언어학에서 특정한 법칙들에 따라 적절하게 구성된 문자열의 집합을 말합니다.

정규 표현식은 다양한 프로그래밍 언어에서 문자열의 검색과 치환을 위해 지원되고 있죠!

그렇기에 현재에 들어서는 많은 프로그래밍 언어에서 기본적으로 정규 표현식의 기능을 제공해주고 있습니다.

Swift도 최근들어 내장 프레임워크로 지원해주더라구요👍

 

자 그럼 조금 더 정규 표현식에 대해 들어가볼까요?

 

정규 표현식에서 패턴이란?

정규 표현식이라는것은 위에서 말했듯 일치하는 텍스트가 준수해야 하는 패턴을 표현하기 위해 특정한 표준의 문법을 의미하기 위해 사용된다고 해요.

즉 여기서 중요한건 패턴입니다.

정규 표현식의 각 문자들은 특별한 의미의 문자로 이해되거나 정규 문자 그 자체로 이해됩니다.

예를들어 영어 알파벳 a와 .(온점)의 조합임 'a.'에서 'a'는 그 자체의 리터럴 문자로 인식되고 '.'는 개행을 제외한 모든 문자가 들어가도 일치시켜주는 메타문자, 즉 위에서 말한 특별한 의미의 문자로 인식됩니다.

정규 표현식에서 메타 문자들을 잘 활용합니다.

주로 정해진 패턴의 텍스트를 식별하기 위해 사용되죠.

위에서 'a.'에서 '.' 메타 문자가 들어왔기에 'a', 'ab', 'a1'등의 다양한 문자들과 일치 시킬 수 있습니다.

 

그럼 한번 더 나아가서 이러한 패턴을 완성 시키는 정규 표현식의 다양한 문법들을 살펴보죠🏃🏻

 

정규 표현식 문법

정규 표현식 문법으로는 POSIX 기반의 기본 문법과 확장 문법이 있습니다.

POSIX가 무엇인지 더 딥하게 들어가면 이 포스팅이 영원히 끝날것 같지 않아.. 여기서 관심사는 아니기에 조금 접어두겠습니다!

또 기회가 되면 딥하게 포스팅을 해볼께요ㅎㅎ

그럼 그냥 여기서 관심사로 생각되는 정규 표현식 핵심 문법들을 보겠습니다.

여기서 문법들은 대부분 위에서 설명했던 메타문자가 하는 역할들이 될거에요.

메타문자 기능 설명
. 문자 일치 개행 문자를 제외한 모든 아무 1개의 문자와 일치시킨다.
[ ] 문자 집합 ":"를 여러번 쓴것과 같으며 사이에 들어오는 문자를 선택하고 "-" 기호를 통해 범위를 지정한다.
예를들어 [a-z]는 a부터 z까지 중 하나를 의미한다.
[^ ] 문자 집합 부정 위의 기능에 반대되는 의미로 사이에 들어오지 않는 문자를 선택한다.
예를들어 [^a-z]는 알파벳 소문자를 제외한 모든 문자를 의미한다.
^ 시작 문자열의 시작을 의미
$ 문자열의 끝을 의미
() 병합 여러 표현식을 하나로 묶을때 사용된다.
예를들어 "a(b¦c)d"는 "abd¦acd"와 같다.
\n n번째 패턴 일치하는 여러 패턴들 중 n번째를 의미하며 n에는 1~9 까지의 자연수만 올 수 있다.
* 0개 이상 0개 이상의 문자를 포함한다.
a*b의 의미는 ab, b, aab, aaaab들을 포함한다.
{m, n} m개 이상 n개 이하 위 *과 비슷하게 갯수를 파악하지만 범위를 준다.
예를들어 a{1,4}b는 a가 무조건 1개 이상 4개 미만으로 들어가 있어야한다.
? 0~1개 *와 조금 달리 없어도 되지만 있다면 1개만 있어야 한다.
예를들어 a?b는 b와 ab 두개만 의미한다.
+ 1개 이상 1개 이상이면 된다.
예를들어 a+b는 ab, aab, aaab, a......b 모두 포함된다.
¦ OR 우리가 알고 있는 OR 연산과 비슷하게 여러개중 하나를 선택한다.
예를들어 ab¦cd는 ab와 cd 모두 의미한다.
Swift에서 자판에 나타날때는 OR 연산자의 기호인 |를 사용한다.
\w 영숫자+_ [A-Za-z0-9_]와 같은 의미로 영어와 숫자, _의 조합
\W 영숫자+_ 부정 [^A-Za-z0-9_]와 같은 의미로 영어와 숫자 그리고 _이 아닌 다른 문자 
\d 숫자 [0-9]와 같은 의미로 숫자를 의미
\D 숫자 부정 [^0-9]와 같은 의미로 숫자가 아닌 문자
\s 공백 공백 문자를 의미
\S 공백 부정 공백이 아닌 모든 문자

이 외에도 조금 더 있지만 주요한것들을 위주로 정리했습니다🙌

이 문법을 활용한 전역적인 예시는 아래 참고자료 링크만 보셔도 아주아주 양이 넘쳐나요!

정규 표현식의 패턴을 익혀보면서 돌려보고 싶다면 아래 링크를 추천합니다👍

https://regexr.com

 

RegExr: Learn, Build, & Test RegEx

RegExr is an online tool to learn, build, & test Regular Expressions (RegEx / RegExp).

regexr.com

 

그럼 이제 진짜 본격적으로 Swift에서 정규 표현식을 나타내는 방법을 알아보겠습니다!

 

Swift에서 정규 표현식 사용하기

주로 어떤 프로그래밍 언어 환경에서든 정규 표현식을 사용하는데는 대부분 유효성 검증을 하는 역할이 들어갈거에요.

 

당연히 문자열의 치환이나 제거나 인덱스 추출 등에서 기여할 수 있는 역할은 무궁무진 합니다🕺🏻

해당 포스팅에서는 어떻게 받아온 문자열이 정규 표현식을 사용해 본인이 체크하고자 하는것과 유효한지 검증해보는 예시를 많이 들어보겠습니다!

정말 다양한 방법들이 있겠지만 대표적으로 4개로 나눠 학습해보겠습니다.

우선 Swift 5.7 이전과 이후로 나눠보려고해요.

이유는 Swift 5.7에서 정규 표현식을 더 쉽게 사용할 수 있도록 개선시켜줬어요.

(근데 아직 실제로 사용해보진 않은게 함정..)

 

1️⃣ range 메서드를 사용하기

가장 간단한 방법이라고도 보이는데요.

Foundation안에 NSRange 타입의 range 메서드를 활용하여 정규 표현식과 매칭되는지 검증할 수 있습니다.

코드로 보는게 더 쉬울것 같네요!

전화번호가 유효한지 검증하는것을 예시로 보겠습니다.

import Foundation

let phoneNum = "010-1111-2222"
let RegexOfPhoneNum = "^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$"

let checkPhoneNum = phoneNum.range(
  of: RegexOfPhoneNum,
  options: .regularExpression
)

이렇게 아주 단순히 range 메서드를 사용할 수 있어요.

range를 통하면 타입은 아래와 같이 Range<String.Index>? 됩니다.

그렇기에 만약 매칭되는 값이 없다면 nil을 반환하죠!

이제 각자 편하게 바인딩하거나 해서 사용하면 됩니다.

 

2️⃣ NSPredicate 사용하기

두번째로는 NSPredicate의 evaluate 메서드를 사용하는것도 있습니다.

@available(iOS 3.0, *)
open class NSPredicate : NSObject, NSSecureCoding, NSCopying {
  ...
  open func evaluate(with object: Any?) -> Bool
  ...
}

Foundation 프레임워크에 속한 NSPredicate 클래스에는 evaluate라는 리턴 타입이 Bool인 메서드가 존재합니다.

이 메서드를 통해 위와 같이 정규 표현식 패턴과 일치하는지 확인할 수 있습니다.

예를들어 아래와 같이 사용할 수 있어요.

import Foundation

extension String {
  func validatePhone() -> Bool {
    let regex = "^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$"
    return NSPredicate(format: "SELF MATCHES %@", regex)
      .evaluate(with: self)
  }
}

// 실제 사용
let isMatchedPhoneNum = "010-1111-2222".validatePhone()

이렇게 기본 String 타입을 확장시켜 구현해놓아도 쓰기 편하더라구요.

 

3️⃣ NSRegularExpression 사용하기

NSPredicate처럼 NSRegularExpression 타입이 기본 Foundation 프레임워크에 존재합니다.

@available(iOS 4.0, *)
open class NSRegularExpression : NSObject, NSCopying, NSSecureCoding {
  public init(pattern: String, options: NSRegularExpression.Options = []) throws
  open var pattern: String { get }
  open var options: NSRegularExpression.Options { get }
  open var numberOfCaptureGroups: Int { get }
  open class func escapedPattern(for string: String) -> String
}

기본 정의를 보면 해당 타입의 이니셜라이저는 throws 메서드입니다.

그리고 이니셜 시 패턴과 옵션을 설정할 수 있죠.

그럼 코드로 보시죠!

extension String{
  func checkMatch(regex: String) throws -> Bool {
    guard let regex = try? NSRegularExpression(pattern: regex) else {
      // 에러 처리
    }
    let result = regex.matches(
      in: self,
      range: NSRange(self.startIndex..., in: self)
    )
    let matchedResult = result.map {
      String(self[Range($0.range, in: self)!])
    }

    return matchedResult.first == self
  }
}

이렇게 String에 확장으로 구현해놓으면 딱 쉽게 사용할 수 있을것 같아요🙌

try print(
  "010-1111-2222".checkMatch(
    regex: "^01([0|1|6|7|8|9])-?([0-9]{3,4})-?([0-9]{4})$"
  )
)

// true

예를들어 위와 같이 사용될 수 있습니다.

 

마지막으로 Swift 5.7에서 소개된 방법을 보시죠!

 

4️⃣ Swift 5.7에서 소개된 Regular Expressions

가장 최신인 Swift 5.7 버전에서 개선된 정규 표현식과 관련된 내용이 나왔습니다.

새로운 Regex 타입을 소개하며 조금 더 쓰기 쉽게 되어 있는것 같아요.

Regex 타입을 공식문서에서 보시죠!

iOS 16 이상에서부터 사용할 수 있어요.

정의와 기본 사용은 아래와 같아요.

struct Regex<Output>
let regex = try Regex("a(.*)b")
let match = "cbaxb".firstMatch(of: regex)
print(match.0) // "axb"
print(match.1) // "x"

여기서 처음보는 Regex 타입이 생겼죠?

이게 나온겁니다 와아....🤪

그냥 쉽게 말해 정규식을 표현하는 하나의 타입이 생겼다고 보면 됩니다.

그런데 소개된것중 가장 큰 장점이라고 생각되는 부분은 DSL입니다.

DSL은 Domain Specific Language의 약자로 도메인 특화 언어라고도 합니다.

Swift스럽게 정규 표현식을 나타낼 수 있는것이죠.

사용을 위해선 RegexBuilder라는 프레임워크를 채택해야합니다.

코드로 보시죠!

import RegexBuilder

let word = OneOrMore(.word)
let emailPattern = Regex {
  Capture {
      ZeroOrMore {
          word
          "."
      }
      word
  }
  "@"
  Capture {
      word
      OneOrMore {
          "."
          word
      }
  }
}

let text = "My email is my.name@example.com."

if let match = text.firstMatch(of: emailPattern) {
  let (wholeMatch, name, domain) = match.output
    
  // wholeMatch is "my.name@example.com"
  // name is "my.name"
  // domain is "example.com"
}

이렇게 Regex 타입을 쉽게 만들 수 있어요.

그리고 word같이 OneOrMore처럼 구현되어 있는 기본 구조체 타입을 이용할 수 있어요.

Optionally, OneOrMore, ZeroOrMore 등 되게 다양한데 이건 구체적으로 다른 포스팅에서 다뤄볼꺼에요👋

위 코드에서는 각 Capture 클래스를 이용해 각 부분을 나눕니다.

즉 아래에서 firstMatch를 통해 세개의 인자에는 전체 매칭, 첫번째 매칭, 두번째 매칭이 순차적으로 들어가게 됩니다.

원하는걸 사용할 수 있구요.

즉 정리하자면 기존 정규 표현식을 Swift에서 사용하려면 조금 친화적이지 않았어요.

그런데 5.7 이후부터는 SwiftUI처럼 사용에서 선언형처럼 사용될 수 있게 용이하도록 개선해준 센스가 보이네요👍

 

마무리

정규 표현식.. 정말 예전부터 쉬운듯 어려운 그런 친구였는데 이번참에 어느정도 학습을 통해 친숙해졌습니다!

하면 된다🕺🏻

 

[참고자료]

https://ko.wikipedia.org/wiki/정규_표현식

 

정규 표현식 - 위키백과, 우리 모두의 백과사전

위키백과, 우리 모두의 백과사전. 노란색 강조 부분은 다음 정규식을 사용했을 때 매치된 것이다. 정규 표현식(正規表現式, 영어: regular expression, 간단히 regexp[1] 또는 regex, rational expression)[2][3] 또

ko.wikipedia.org

https://eunjin3786.tistory.com/12

 

[Swift] Swift에서 정규표현식(Regular Expression)을 이용하기

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 extension String{     func getArrayAfterRegex(regex: String) -> [String] {                 do {             let regex =..

eunjin3786.tistory.com

https://tngusmiso.tistory.com/62

 

[Swift] 코딩테스트 보다가 열 받아서 정리하는 Swift 정규식 - NSRegularExpression (Regex)

문자열 문제 진짜 어렵다!!!!!!!!! 매번 구글링 하지 말고 정리해 둬야 겠다는 필요성을 느꼈다... Swift 주의사항 문자열에서 역슬래쉬( \ )는 연산자 역할을 하므로, \ 를 문자 자체로 사용하고 싶은

tngusmiso.tistory.com

https://www.hackingwithswift.com/articles/249/whats-new-in-swift-5-7

 

What’s new in Swift 5.7

Or as I’ve started calling it, what isn’t new in Swift 5.7?

www.hackingwithswift.com

https://developer.apple.com/documentation/swift/regex

 

Apple Developer Documentation

 

developer.apple.com

https://developer.apple.com/documentation/RegexBuilder

 

Apple Developer Documentation

 

developer.apple.com