正则表达式
Swift至今为止并没有在语言层面上支持正则表达式。我们可以自定义类似 =~
的操作符来进行正则匹配。在Cocoa中我们可以使用 NSRegularExpression
来做正则匹配,因为做的是字符串正则匹配,所以 =~
左右两边都是字符串。我们可以先写一个接受正则表达式的字符串,以此生成 NSRegularExpression
对象。一个简单的实现可能是下面这样的:
//在Cocoa中使用NSRegularExpression来做正则匹配。
struct RegexHelper {
let regex : NSRegularExpression
init (_ pattern: String) throws {
try regex = NSRegularExpression(pattern: pattern,
options: .caseInsensitive)
}
func match(_ input: String) -> Bool {
let matches = regex.matches(in:input,
options:[],
range: NSMakeRange(0, input.characters.count))
return matches.count > 0
}
}
在使用的时候,比如我们要匹配一个邮箱地址,我们可以这样来使用:
let mailPattern = "^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$"
let matcher : RegexHelper
do {
matcher = try RegexHelper(mailPattern)
let maybeMailAddress = "[email protected]"
if matcher.match(maybeMailAddress) {
print("有效的邮箱地址")
}
} catch {
}
// 输出:有效的邮箱地址
现在我们有了方便的封装,接下来实现一下 =~
吧:
infix operator =~: ComparisonPrecedence
func =~(lhs: String, rhs: String) -> Bool {
do {
return try RegexHelper(rhs).match(lhs)
}catch _ {
return false
}
}
这样我们就能使用类似于其他语言的正则匹配的方法了:
let mailPattern = "^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$"
let maybeMailAddress = "[email protected]"
if maybeMailAddress =~ mailPattern {
print("有效的邮箱地址")
}
// 输出:有效的邮箱地址
模式匹配
从概念上讲正则匹配只是模式匹配的一个子集,但是在 Swift 里现在的模式匹配还很初级,也很简单,只支持最简单的相等匹配和范围匹配。在 Swift
中,使用 ~=
来表示模式匹配的操作符。如果我们看看 API 的话,可以看到这个操作符有下面几种版本:
func ~=<T: Equatable>(a: T, b: T) -> Bool
func ~=<T>(lhs: _OptionalNilComparisonType, rhs: T?) -> Bool
func ~=<I: intervalType>(pattern: I, value: I.Bound) -> Bool
从上至下在操作符左右两边分别接收判等的类型,可以与 nil
比较的类型,以及一个范围输入和某个特定值,返回值很明了,都是是否匹配成功的 Bool
值。你是否有想起些什么呢...没错, 就是 Swift 中非常强大的 switch,我们来看看 switch 的几种常见用法吧:
- 可以判等的类型的判断
let password = "akfuv(3"
switch password {
case "akfuv(3": print("密码通过")
default : print("密码通过")
}
- 对 Optional 的判断
let num: Int? = nil
switch num {
case nil: print("没值")
default : print("\(num)")
}
- 对范围的判断
let x = 0.5
switch x {
case -1.0...1.0: print("区间内")
default : print("区间外")
}
Swift 的 switch
就是使用了 ~=
操作符进行模式匹配,case
指定的模式作为左参数输入,而等待匹配的被 switch
的元素作为操作符的右侧参数。只不过这个调用是由Swift隐式地完成的。于是我们可以发挥想象的地方就很多了,比如在 switch
中做case
判断的时候,我们完全可以使用我们自定义的模式匹配方法来进行判断,有时候这会让代码变得非常简洁,具有条理。我们只需要按照需求重载 ~=
操作符就行了,接下来我们通过一个使用正则表达式做匹配的例子加以说明。
1.首先我们要做的是重载 ~=
操作符,让它接受一个 NSRegularExpression
作为模式,去匹配输入的 String
:
//自定义模式匹配
//1 重载 ~= 操作符 让它接收一个 NSRegularExpression 作为模式,去匹配输入的string
func ~=(pattern: NSRegularExpression,input: String) -> Bool {
return pattern.numberOfMatches(in: input,
options: [],
range: NSMakeRange(0, input.characters.count)) > 0
}
2.然后为了简便起见,我们再添加一个将字符串转换为 NSRegularExpression
的操作符 (当然也可以使用 StringLiteralConvertible
,但是它不是这个 tip 的主题,在此就先不使用它了):
//2 添加一个将字符串转化为 NSRegularExpression 的操作符(也可以使用StringLiteralConvertible)
prefix operator ~/
prefix func ~/(pattern: String) -> NSRegularExpression {
return try! NSRegularExpression(pattern: pattern, options: [])
}
3.现在,我们在 case
语句里使用正则表达式的话,就可以去匹配被 switch
的字符串了:
let contact = ("http://onvcat.com", "[email protected]")
let mailRegex: NSRegularExpression
let siteRegex: NSRegularExpression
//将字符串转化为 NSRegularExpression (正则表达式)
mailRegex =
~/"^([a-z0-9_\\.-]+)@([\\da-z\\.-]+)\\.([a-z\\.]{2,6})$"
siteRegex =
~/"^(https?:\\/\\/)?([\\da-z\\.-]+)\\.([a-z\\.]{2,6})([\\/\\w \\.-]*)*\\/?$"
//swfit里的switch 就是使用~=操作符进行模式匹配,case指定的模式作为左参数输入,而等待匹配的被switch的元素作为操作符的右参数输入。
//这个调用是由swift隐式地完成的。这里重载~=操作符,就可以按照我们自定义的模式进行匹配了
switch contact {
case (siteRegex, mailRegex):
print("同时拥有有效的网站和邮箱")
case (_, mailRegex):
print("只拥有有效的邮箱")
case (siteRegex, _):
print("只拥有有效的网站")
default:
print("啥都没有")
}
// 输出:同时拥有有效的网站和邮箱
where 和 模式匹配
where
关键字在 Swift 中非常强大,但是往往容易被忽视。在这一节中,我们就来整理看看 where
有哪些使用场合吧。
1.在 switch
语句中,我们可以使用 where
来限定某些条件 case
:
let name = ["王小二", "张三", "李四", "王二小"]
name.forEach {
switch $0 {
case let x where x.hasPrefix("王"):
print("\(x)是笔者本家")
default:
print("你好, \($0)")
}
}
//输出:
//王小二是笔者本家
//你好, 张三
//你好, 李四
//王二小是笔者本家
这可以说是模式匹配的标准用法,对 case
条件进行限定可以让我们更灵活地使用 switch
语句。
2.在 for
中我们也可以使用 where
来做类似的条件限定:
let num: [Int?] = [48, 99, nil];
let n = num.flatMap {$0} //
print(n)
for score in n where score > 60 {
print("及格啦 - \(score)")
}
//输出: 及格啦 - 99
和 for
循环中类似,我们也可以对可选绑定进行条件限定。不过 在 Swift 3 中,if let
和 guard let
的条件不再使用 where
语句,而是直接和普通的条件判断一样,用逗号写在 if
或者 guard
的后面 :
num.forEach {
if let score = $0, score > 60 {
print("及格啦 - \(score)")
}
else {
print(":(")
}
}
// 输出:
// :(
// 及格啦 - 99
// :(
3.在泛型中对方法的类型进行限定。比如在标准库里对 RawRepresentable
协议定义 !=
运算符定义时:
public func !=<T: RawRepresentable where T.RawValue: Equatable>(lhs: T, rhs: T) -> Bool
这里限定了 T.RawValue
必须要遵守 Equatable
协议,这样我们才能通过对比 lhs
和 rhs
的 rawValue
是否相等,进而判断这两个 RawRepresentable
值是否相等。如果没有 where 的保证的话,下面的代码就无法编译。同时,我们也限定了那些 rawValue
无法判等的 RawRepresentable
类型不能进行判等。
public func !=<T: RawRepresentable where T.RawValue: Equatable>(lhs: T, rhs: T) -> Bool {
return lhs.rawValue != rhs.rawValue
}
4.在 Swift 2.0 中,引入了 protocol extension
。在有些时候,我们会希望一个协议扩展的默认实现只在某些特定的条件下适用,这时我们就可以用 where
关键字来进行限定。标准库中的协议扩展大量使用了这个技术来进行限定,比如 Sequence
的 sorted
方法就被定义在这样一个类型限制的协议扩展中:
extension Sequence where Self.Iterator.Element : Comparable {
public func sorted() ->[Self.Iterator.Element]
}
很自然,如果 Sequence
(比如一个 Array
) 中的元素是不可比较的,那么 sorted
方法自然也就不能适用了:
let sortableArray: [Int] = [3,1,2,4,5]
let unSortableArray: [Any?] = ["Hello", 4, nil]
print(sortableArray.sorted()) //[1, 2, 3, 4, 5]
print(unSortableArray.sorted()) //编译错误