面白きことは良きことなり

拙く未熟なiOSエンジニアの備忘録と戯言

NSPredicateを使うと@を全角半角区別してくれない

emailのvalidationメソッド

業務中、emailのvalidationメソッドをいじることになって知った話

下記のようなコードでemailのvalidationチェックを正規表現を行なっていたのだが、 @ が全角でも true が返ってきてしまうことが発覚した。(emailの正規表現に関しては正確ではないので、そこは雰囲気で)

func isValidEmail(string: String) -> Bool {
    let emailReg = "[A-Za-z0-9._%+-]+[A-Za-z0-9]+@[A-Za-z0-9]+[A-Za-z0-9.-]+\.[A-Za-z]{2,6}"
    let predicateMail = NSPredicate(format:"SELF MATCHES %@", emailReg)
    return predicateMail.evaluate(with: string)
}

Androidでは全角半角区別されていた

同じプロジェクトのAndroid側では似た処理を行なっていたが、 @ が半角の時のみvalidとして扱われていたので、その振る舞いに疑問を覚え、Discord上のios-developers-japanコミュニティに質問投げた。

f:id:aryzae:20171213001954p:plain

NSPredicate 使ってることもあって、iOSでもmacOSでももはや @ は全角半角区別されない仕様なのかと思っていた。

SwiftならNSRegularExpressionがある

そもそも NSPredicateを前任が使って書いていたり、自分がiOS上で正規表現を使ったことなくて知識が乏しく気づけなかったのだが、NSRegularExpressionクラスというのがあるのを知らなかった。(見たことある気もするけど、覚えてない)

んでこいつを使えば、下記のように判定はできるということを教えてもらった。

// loveeさんから提示のコード
let stringA = "123@456"
let stringB = "123@456"

let rangeA = stringA.range(of: "@[0-9]", options: .regularExpression, range: nil, locale: nil) //.some
let rangeB = stringB.range(of: "@[0-9]", options: .regularExpression, range: nil, locale: nil) //nil
// omochimetaruさんから提示のコード
func isValidEmailFormat2(string: String) -> Bool {
    let emailReg = "[a-zA-Z0-9._%+-/:~$^()&!']+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,4}"
    
    // valid regexp
    let regexp = try! NSRegularExpression.init(pattern: emailReg, options: [])
    
    let nsString = string as NSString
    let matchRet = regexp.firstMatch(in: string, options: [], range: NSRange.init(location: 0, length: nsString.length))
    return matchRet != nil
}

また、NSPredicateよりNSRegularExpressionの方が標準的だろうというお話も出た。

最終的には…

NSRegularExpressionを使えば、@の全角半角の判別を正常にできるのがわかったわけだが、諸事情(時間的と引き継いだコードで振る舞いを変えづらい)により、文字列がascii文字かの判定を別途足し方式でvalidationを行なった。

var email1 = "hoge@gmail.com"
email1.canBeConverted(to: .ascii)   // true

var email2 = "hoge@gmail.com"
email2.canBeConverted(to: .ascii)   // false