Swift2.0 中的String(六):正则匹配

Swift中的字符串,第六篇,正则匹配。其他的几篇传送门:

对于字符串的操作来说,正则匹配算是很重要的一项应用了。虽然其实我不太用,但偶尔需要的时候还是感到这东西真挺有用的。在网上找了找,对于iOS中使用正则,基本上都是说有三种方法,可惜给的Swift源码比较少,版本问题也不能直接跑,于是自己动手做一遍。总结了下其实所谓的三种方法其中两种还是很有局限性的。

首先我们定义好待匹配的字符串和pattern:

let str = "18800002222---13144445555"
let pattern = "1[3|5|7|8][0-9]\\d{8}"

简单示范,就不搞太复杂了。这个pattern是手机号码的正则,因为准备demo匹配多个结果,没有加开头和结尾的限制。

你看到的是非授权版本!爬虫凶猛,请尊重知识产权!

转载请注明出处:http://conanwhf.github.io/2015/12/09/Swift_String_6/

访问原文「Swift2.0 中的String(六):正则匹配」获取最佳阅读体验并参与讨论

String直接支持的匹配方式

严格地来说,只有这一种才算是真正的“Swift中的正则”,其他的都是使用了OC继承过来的类。而这种方法也很简单,就是简单的使用rangOfString而已。

str.rangeOfString(pattern, options: NSStringCompareOptions.RegularExpressionSearch)

很简单吧?只是把option的参数改成RegularExpressionSearch就行了。这种方法会得到一个Range类型的返回值,是匹配到的第一个子串的位置。在这个例子中,返回0..\<11;如果找不到匹配,返回nil

使用NSPredicate的方式

这是我最不推荐的方式。NSPredicate本身是数据查找、过滤的强大工具,他自己本身有一套语法,而正则只是其中的一小部分方式:

let pred: NSPredicate = NSPredicate(format: "SELF MATCHES %@", pattern)
pred.evaluateWithObject(str)

这个方法返回一个bool来说明是否匹配到,而且只能匹配全字符串(在这个例子中就会返回false),比第一种方法还复杂,除非是想用NSPredicate相关的奇技淫巧,否则还是用rangOfString要好得多。

使用NSRegularExpression的方式

NSRegularExpression是专门的正则匹配类型,如果你需要更专业的匹配,推荐使用这种方法。
首先需要声明一个新的NSRegularExpression类型,并且设定它的匹配pattern:

var expr              = try NSRegularExpression(pattern: pattern, options: [])
expr.pattern    // Show the pattern

这一步可以看到正则表达式已经设置好了,可以使用这个变量对各种字符串进行匹配。
匹配的方法有两种,一种是Block的方式(在Swift中可以近似于闭包),另外一种是普通的(我也不知道是不是可以说成non-block)。

插播吐个槽:_这个什么鬼Block真的搞得我好困惑啊!在C里面的Block函数是指那些阻塞进程的,而在OC里面是个反的!有callback专门做异步的!搞得我每次看到都要反应一下!求问为啥这么定义?!😖_

另外我在网上看到的示例中某些API都已经找不到了,这个类的完善看来也经历了一些过程……言归正传。Block的方法还要用到Callback来拿返回值,懒得用,我们的数据量反正也不大,先拿普通用法来解释:

var range             = NSMakeRange(0, str.characters.count)
var res               = expr.firstMatchInString(str, options: NSMatchingOptions.ReportProgress, range: range)
print(res?.range)

这是最简单常用的匹配,匹配第一个字串,返回一个封装好的类。如果没有结果,返回nil。通常我们只需要拿到它的range属性,注意:!!!这里的range是NSRange而不是Range,它的结构是(start, length)!!!

然后是另外一种获取全部匹配的方法:

var resAll            = expr.matchesInString(str, options: NSMatchingOptions.ReportProgress, range: range)
resAll.forEach{ print($0.range) }

这个方法返回一个数组,内容是每一个匹配结果。使用NSRegularExpression是唯一一种能够获得所有匹配结果的方法,如果没有匹配,则返回一个空数组。
同时我们还可以直接获取匹配到的结果的数量:

expr.numberOfMatchesInString(str, options: NSMatchingOptions.ReportProgress, range: range)

很方便的另一个方法,是将所有匹配替换为另外的字符串:

var newStr = NSMutableString(string: str)
expr.replaceMatchesInString(newStr, options: [] , range: range, withTemplate: "phoneNum")
newStr

结果newStr变成了_“phoneNum—phoneNum”_。这里要用NSMutableString当作Input,是因为它本身是给OC~NSString系统用的,而NSString是固定memory的,替换可能改变字符串长度,所以需要强制要求提供一个NSMutableString的入口。Swift中的String类型是没有这种问题的。
这个方法其实靠rangeOfString + stringByReplacingCharactersInRange 也能做到,就是麻烦点儿。参数中的Template应该还有别的动态用法,我没深入研究了。

URL的匹配和解析

说了这么多,最后顺便来一点URL吧。URL的解析在实用中也是很常见的,虽然也是可以使用正则表达式,但其实它有自己的接口来实现parser。下面就是范例代码:

let url        = NSURL(string: "http://weibo.com/u/1864854042?sudaref=conanwhf.github.io&test=???&lalalvy=irobot")!
let components = NSURLComponents(URL: url, resolvingAgainstBaseURL: false)
let item       = components?.queryItems
item?.forEach { print($0.name, $0.value!) }

最后打印出结果:
sudaref conanwhf.github.io
test ???
lalalvy irobot

很方便!

至此,字符串的部分应该是差不多了,如果说还有待研究的部分,那就是xml和json数据解析了吧。String本身在C语言中是作为数据存储万金油的存在,而在Swift中,它的数据层面被淡化了很多,同时多了很多新特性。学习它的时候,我常常会想,为什么要这么设置API,和旧的API相比这么做有什么好处,为什么会有这样的属性,什么情况下会用到……希望能通过这样的思考弥补自己对于面向对象概念的不足,慢慢找到感觉。