Regex Explanation with NSPredicate

DevMinion·2022년 7월 28일
2

정규 표현식과 NSPredicate에 대해서 알아보쟈🙌

이전 포스팅인 Unit Test에서 사용한 예제에 비밀번호 정규 표현식을 사용했다. 우리가 어느 서비스에 회원가입을 할 때, 회원가입을 진행할 이메일이나 비밀번호에 규칙이 있는 것을 모두 한 번쯤 보았을 것이다.

이렇게 원하는 조건을 걸어줄 수 있다. 먼저 정규 표현식을 알아보고 비밀번호 생성 예제와 함께 직접 확인해보자.

Regex Explanation

기본적인 표현 방식은 이러하다.

Basic Regex

대문자

"[A-Z]"

소문자

"[a-z]"

숫자

"[0-9]" // 0-9까지 모든 숫자. 조절하여 사용 가능.

대문자+소문자

"[A-Za-z]"

대문자+소문자+숫자

"[A-Za-z0-9]"

특수문자

"[~!@#$%^&*]" // 원하는 특수문자를 넣어준다.

최소 하나 이상의 [조건]

"(?=.*[조건])"

만약 하나 이상의 대문자를 포함하고 싶다면,
"(?=.*[A-Z])"

위에서 배운 정규식을 아래의 코드로 정규식에 부합하는지 확인할 수 있다.

let testStrings = "Hello123!@" // 확인하려는 문자열
let reg1 = "(?=.*[a-z])" // 조건

print(testStrings.range(of: reg1, options: .regularExpression) != nil)
// true

예제

코딩테스트

쉽고 간단한 문제를 통해 알아보자.
프로그래머스LV1 - 문자열 다루기 기본

이 문제를 정규식을 통해 풀어보도록 하자.

import Foundation
func solution(_ s:String) -> Bool {
    let reg = "^[0-9]*$"
    if s.count == 4 || s.count == 6 {
        return (s.range(of: reg, options: .regularExpression) != nil)
    }
    return false
}

이렇게 풀어 보았다. 주어진 문자열을 간단하게 count함수로 4자 혹은 6자인지 확인하고 정규 표현식으로 숫자만 있는지 검출하여 출력하였다.

결과는? 당연히 틀렸으면 안올렸찌

이렇게 응용하여 풀 수 있다는 뜻이지 해당 문제는 훨씬 빠르게 풀 수 있는 문제이다. 실행 시간이 1ms를 넘어간다.

비밀번호

"^(?=.*[A-Za-z])(?=.*[~!@#$%^&*])(?=.*[0-9]).{8,16}"

이렇게 주어졌다고 해보자. 이제 하나씩 분해해보자.

(?=.*[A-Za-z]): 대문자 혹은 소문자 알파벳 하나 이상 포함.
(?=.*[~!@#$%^&*]): 특수문자 하나 이상 포함.
(?=.*[0-9]): 숫자 0부터 9까지 하나 이상 포함.
{8,16}: 8글자 이상, 16글자 이하.

종합하면 해당 식은 8글자 이상, 16글자 이하, 하나 이상의 대문자 혹은 소문자,하나 이상의 숫자, 그리고 하나 이상의 특수문자로 이루어 져야함.이다.
저번 포스팅에 사용한 Unit Test 프로젝트에서 한번 실행해보자.

func isValidPassword(password: String) -> Bool {
        let passwordRegEx = "^(?=.*[A-Za-z])(?=.*[~!@#$%^&*])(?=.*[0-9]).{8,16}$"
        let passwordTest = NSPredicate(format: "SELF MATCHES %@", passwordRegEx)
        return passwordTest.evaluate(with: password)
    }

여기서 등장하는 NSPredicate는 뒤에서 알아보도록 하자.
이렇게 정규식을 세운 비밀번호 검증 함수를 작성하고,

func testRegisterValidator_WhenValidPasswordProvided_ShouldReturnTrue() {
        // Given
        let user = User(password: "aASs223d3!")

        // When
        let isValidPassword = sut.isValidPassword(password: user.password)
        
        // Then
        XCTAssertTrue(isValidPassword, "대문자, 소문자, 숫자, 특수문자를 조합하여 8자 이상 16자 이하 이어야 합니다.")
    }

Unit Test 함수를 작성한다. 테스트를 위해 주어진 비밀번호는 "aASs223d3!"이다. 당연하게도 우리가 세운 조건에 모두 만족하기에 무리없이 테스트는 통과한다. 그럼 비밀번호를 조건에 맞지않게 바꿔보자.

let user = User(password: "AS223asS213")

주어진 비밀번호에는 특수문자가 포함되어 있지 않다. 테스트를 돌려보면?


당연히 실패가 나오는 것을 확인할 수 있다.

NSPredicate란?

위의 코드에서 NSPredicate가 나오는 것을 확인할 수 있다. 이를 자세히 알아보자.

NSPredicate는 클래스로 애플 공식 문서에 따르면, fetch 혹은 메모리 내에서 어떤 값을 가져올때 메모리의 filter에 대한 제약 조건이라고 한다.

해당 클래스의 사용법에는 CoreData에서 특정 데이터를 가져올때 사용하거나, NSPredicate의 메소드인 evaluate(with:)을 사용하여 정규식을 판별하기 위해 사용하는 두 가지 사용법이 있다. 우리는 이 두번째 사용법을 위해 NSPredicate를 사용하는 것이다.

NSPredicate의 format속 문자를 살펴보면 "SELF MATCHES %@"라고 표현이 되어 있는 것을 확인할 수 있다. 여기서 %@는 Objective-C에서 사용하는 형식 지정자이다. 문자열을 뜻한다.

let passwordTest = NSPredicate(format: "SELF MATCHES %@", passwordRegEx)

우리가 코드에서 넣어준 passwordRegEx를 저 %@자리에 넣어 준다. 그냥 해당 %@자리에 Regex 문자열을 적어도 상관없지만, 그렇게 한다면 확장성이 떨어지는 코드이기에 이렇게 작성한다. 이렇게 해서 predicate를 만들어 evaluate와 함께 조건을 확인한다.

그렇다면 이어서 evaluate(with:)도 알아야겠군.

evaluate메소드는 우리가 지정한 개체가 predicate에서 지정한 조건과 일치하는지 여부를 bool타입으로 반환하여 알려준다. 즉, 우리가 사용한

return passwordTest.evaluate(with: password)

이 코드를 해석하면 NSPredicate클래스 인스턴스인 passwordTest의 조건을 테스트를 위해 입력받은 매개변수인 password가 일치하는지 일치하지 않는지를 반환하는 것이다.

References(Thx🤙)

evaluate(with:) - Apple
NSRegularExpression - Apple
How to implement a regex for password validation in Swift? - Stack Overflow

profile
iOS를 개발하는 미니언

0개의 댓글