零. 前言
在前面写的三篇文章:iOS分类 同名方法自动检测脚本、iOS瘦身——移除无用资源的LSUnusedResources源码分析与优化和iOS通知/KVO移除 自动检测脚本
中,核心功能都用到了正则表达式,可以说,正则是模糊匹配的最佳选项了,所以我尝试从入门介绍到应用场景,整理一下正则表达式在iOS开发场景中的作用。
一. 正则介绍
本章引用于正则菜鸟教程,会对该教程有个入门级的概括,加上一些自己的举例和见解。
正则表达式(regular expression)描述了一种字符串匹配的模式(pattern),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等。
例如:
runoo+b,可以匹配 runoob、runooob、runoooooob 等,+ 号代表前面的字符必须至少出现一次(1次或多次)。
runoo*b,可以匹配 runob、runoob、runoooooob 等,* 号代表字符可以不出现,也可以出现一次或者多次(0次、或1次、或多次)。
colou?r 可以匹配 color 或者 colour,? 问号代表前面的字符最多只可以出现一次(0次、或1次)。
通过使用正则表达式,可以:
(1) 提取特定符合规则的文本。
例如,可以测试输入字符串,以查看字符串内是否出现电话号码模式或信用卡号码模式。通常运用于日常开发需求中。
(2) 寻找特定规则的子字符串。
可以查找文档内或输入域内特定的文本,通常运用于提升debug或者阅读源码效率。
(3) 批量替换文本。
可以使用正则表达式来识别文档中的特定文本,完全删除该文本或者用其他文本替换它,通常运用于提升开发工作效率。
除了上面提到的例子之外,还有很多规则,熟练掌握他们,就可以运用于日常开发、开发需求、开发一些内部小工具中,有助于提升工作效率。下面来介绍常用的正则表达式规则。
二. 正则语法
1. 正则常用语法
每个工具都有自己的说明书,语法就是正则的说明书,但这个说明书非常简单,通常不用一天时间就能入门基础的语法,足以运用于80%的工作案例中。
正则表达式是由普通字符(例如字符 a 到 z、数字0到9、一些符号如_";等等)以及特殊字符(称为"元字符")组成的文字模式。模式描述在搜索文本时要匹配的一个或多个字符串。正则表达式作为一个模板,将某个字符模式与所搜索的字符串进行匹配。
普通字符就是你所能在键盘上打出的所有字符,而特殊字符则有一定的含义,如上面的例子中:+就是匹配一个或多个,*就是匹配零个或多个,等等。
下面会列举一些常用特殊字符和意义:
首先是一些非打印字符,也就是一些键盘上打不出来,但又有具体意义的字符,如:换行符\n
、空白字符\s
等,红框标出来的就是比较常用的非打印字符。
而特殊字符则会有一个抽象的意义,他并不是某个具体的字符,而是一种基于正则规则的抽象字符。
下面会用匹配的例子去加深对特殊字符的印象:
// *的举例(匹配零次或多次)
正则:zo*
字符:zoe zoo zo z ao
结果:zo zoo zo z 不匹配,因为*代表前面的字符(o)可出现也可不出现,但z必须出现
// +的举例(匹配一次或多次)
正则:zo+
字符:zoe zoo zo z ao
结果:zo zoo zo 不匹配 不匹配,因为+代表前面的字符(+)至少出现一次,但z必须出现
// .的举例(匹配换行符外的任意字符)
正则:zo.
字符:zoe zoo zo z ao
结果:zoe zoo 不匹配 不匹配 不匹配,因为.匹配的是一个字符,只要zo开头且后面接着字符就可以匹配
扩展:.*代表着中间匹配任意字符,如z.*o可以匹配zeeeeeeeeeeeo,但z.o只能匹配zeo;类似的,.+代表中间必须有至少一个任意字符。
// []的举例(相当于集合,在这集合内均可匹配)
正则:[a-z]o
字符:zoe zoo zo z ao
结果:zo zo zo 不匹配 ao,相当于o前面接任意一个字符都可以匹配
// ?的举例(匹配零次或一次)
正则:zo?
字符:zoe zoo zo z ao
结果:zo zo zo z 不匹配,相当于匹配z或者zo
// +和?结合的举例(?表示非贪婪,即尽可能少地匹配)
正则:zo+?
字符:zo zoo zooooooo
匹配:都是zo,因为需要匹配尽可能少的o
扩展:如果不加?,则代表贪婪,即尽可能多的匹配,如zo+可以匹配zooooooooooo
// ^和$结合使用的举例(用于匹配开始字符和结束字符)
正则:^z.o$
字符:zeo azeo zo
结果:zeo 不匹配 不匹配,因为这个字符串一定要以z开头,以o结尾,且中间要有一个字符
// ^和[]结合使用的举例(表示除这个集合外的任意字符)
正则:[^a-z]o
字符:ko 9o _o
结果:不匹配 9o _o
// {}使用举例(表示某个字符出现匹配某个区间次)
正则:zo{1,2}
字符:zo zoo zooo z
结果:zo zoo zoo 不匹配
扩展:{,2}表示{0,2},{1,}表示{1,正无穷}
// |使用举例(表示或)
正则:(zoo|aoo)m
字符:zoom aoom zoo zm
结果:zoom aoom 不匹配 不匹配
// \使用举例(表示转义字符)
正则:zo\.
字符:zo. zoo
结果:zo. 不匹配,因为\.是匹配.这个字符,没有其他意义
// ()使用举例(捕获分组,匹配到括号的字符,以后可以后续使用)
正则:(.*?)\ssays?:\s(.*)
字符:Tom and Jerry says: Yes
结果:如果遍历匹配结果,则分别为Tom and Jerry,Yes
// (?:regex)使用举例(非捕获分组,匹配到括号的字符,但不可以后续使用)
正则:[a-z]+(?:\d)
字符:abc123xyz
结果:abc1,括号内可以匹配字符
/** 环视匹配 **/
// 环视匹配,只匹配位置,不匹配字符,相当于一种条件限制,有时候用到()但是不希望匹配这些字符,就可以使用环视匹配。
// (?=regex)使用举例(顺序肯定环视,从左向右看,后面需要满足括号内的正则,即满足regex前面的位置)
正则:[a-z]+(?=\d)
字符:abc123xyz
结果:abc,即只匹配数字前的字母字符
// (?!regex)使用举例(顺序否定环视,从左向右看,后面不能出现括号内的正则)
正则:[a-z]+(?!\d)
字符:abc123xyz
结果:ab和xyz,因为c后面跟了数字,所以不能匹配
// (?<=regex)使用举例(逆向肯定环视,从右向左看,前面需要满足括号内的正则)
正则:(?<=\d)[a-z]+
字符:abc123xyz
结果:xyz,即只匹配数字后的字母字符
// (?
除此之外,还有一些比较常用的元字符,框出了比较常用的,可以参考一下。
掌握了上面的语法之后,我们就可以匹配一些比较常用的场景了:
^[a-zA-Z0-9_]+$ // 所有包含一个以上的字母、数字或下划线的字符串
^[1-9][0-9]*$ // 所有的正整数
^\-?[1-9][0-9]*$ // 所有的整数
^[-]?[0-9]+(\.[0-9]+)?$ // 所有的浮点数
使用正则只需记住两点就可以掌握了:
提取特征(共同点)
排除例外
在日常开发需求中,会遇到很多诸如匹配URL、验证电话号码是否有效等等的需求,这些都是可以用正则解决的,仅需用这两步,兵来将挡水来土掩即可。
https?://[^\s]* // 匹配HTTP(s) URL,特征:都由http(s)://开头,例外:URL中间不含空格
^[1][^012][0-9]{9}$ // 匹配国内手机号,特征:肯定是11位数字,第一位肯定是1,第二位肯定不是012
[1-9][0-9]{4,} // 匹配QQ号,特征:5位以上,第一位肯定不是0
2. 拆分并读懂正则
正则虽然很方便,但是他的可读性对于新手来说并不是那么友好,那么就需要一个比较方便的读懂正则的方法了。
第二节和第三节内容截取自JS 正则迷你书,这本书很详细介绍了正则,如果感兴趣可以去看看。
首先看一下正则的优先级
在拆分正则时,应遵循优先级从高到低去拆解。
分析一下正则ab?(c|de*)+|fg
,先看括号,显然(c|de*)
是一个整体,再看竖杠,竖杠左右各为一个整体,所以原正则拆分为ab?(...)+、fg
。
分析括号里面的内容:拆分为c、de*
,代表出现c或者出现d的时候e为0或多,且这个整体至少出现一次,至此就可以得到可视化逻辑:
再举一个例子,简单的身份证识别:^(\d{15}|\d{17}[\dxX])$
由优先级来看,可以看成^(...)$
,括号里面看最低优先级的竖杠,可以分为\d{15}
和\d{17}[\dxX]
,分别代表15位数字和17位数字后面跟着x或X,可视化逻辑如下:
最后举一个较为复杂的,IPV4地址:
^((0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])\.){3}(0{0,2}\d|0?\d{2}|1\d{2}|2[0-4]\d|25[0-5])$
看上去很吓人,但我们还是遵循优先级来看,可以发现有两个并列的括号,^(aaa){3}(bbb)$
,即以aaa连续出现3次为开头,以bbb出现为结尾的字符串。
观察前后两个括号内容,发现前面的括号只是多了一个\.
,其他内容一样,所以可以看成(xxx\.){3}(xxx)
,即xxx.xxx.xxx.xxx
,其中xxx的正则相同。
再看xxx到底是啥,将其拆分为0{0,2}\d
、0?\d{2}
、1\d{2}
、2[0-4]\d
、25[0-5]
,其中他们是“或关系”,分开分析如下:
0{0,2}\d
,匹配一位数,包括 "0" 补齐的。比如,"9"、"09"、"009";
0?\d{2}
,匹配两位数,包括 "0" 补齐的,也包括一位数;
1\d{2}
,匹配 "100" 到 "199";
2[0-4]\d
,匹配 "200" 到 "249";
25[0-5]
,匹配 "250" 到 "255"。
不难得出,正则的意思是xxx.xxx.xxx.xxx,其中xxx为:0-255,前面可补位可无补位,可视化形式如下:
三. 编写准确高效的正则
1. 编写更准确的正则
使用正则之前,先问自己:是否能使用正则、是否有必要使用正则、是否有必要构建一个复杂的正则。
如果原生语言的API能顺利解决问题,那便没有必要使用正则;如果可以拆分为多个可读性更强的正则,那便没必要构建太复杂的正则。
但如果觉得使用正则会使得效率提升很多,那编写一个更准确的正则无疑是一个正确的选择。
书中举了一个例子,需要匹配一个固定电话号码:
055188888888
0551-88888888
(0551)88888888
根据前面的步骤,我们需要提取特征,但不能太着急地一次性将三种特征归纳一起,不然很容易将字符是否出现的情况归纳为^\(?0\d{2,3}\)?-?[1-9]\d{6,7}$
,虽然也能匹配上面的字符串,但是会有一些异常情况进入匹配,如(0551-88888888
。所以我们要采用分步提取再化简。
这三种形式的号码特征都很明显,分别为:
^0\d{2,3}[1-9]\d{6,7}$
^0\d{2,3}-[1-9]\d{6,7}$
^\(0\d{2,3}\)[1-9]\d{6,7}$
显然地,这三者为“或”关系,所以得正则:
^0\d{2,3}[1-9]\d{6,7}$|^0\d{2,3}-[1-9]\d{6,7}$|^\(0\d{2,3}\)[1-9]\d{6,7}$
再提取公共部分:
^(0\d{2,3}|0\d{2,3}-|\(0\d{2,3}\))[1-9]\d{6,7}$/
进一步化简:
^(0\d{2,3}-?|\(0\d{2,3}\))[1-9]\d{6,7}$
可视化形式如下:
2. 编写高效的正则
本章节摘自JS 正则迷你书、改进正则表达式的性能
2.1 正则的回溯
这篇文章总结道,回溯的两个要点:
1.如果需要在“进行尝试”和“跳过尝试”之间选择:对于匹配优先量词(贪婪量词),引擎会优先选择“进行尝试”,而对于忽略优先量词(懒惰量词),会选择“跳过尝试”。
2.LIFO(后进先出)原则。
所谓回溯,就是在匹配的位置,正则引擎为了满足其他匹配而不得不进行的字符释放,顺序是从右往左回退,所以叫“回溯”,我叫它“回吐”,把前面“吃到的”字符“吐”一部分出来。
假设正则为ab{1,3}c
,目标字符串为"abbbc",则不会回溯
如目标字符串为"abbc",就会有回溯
假设正则为ab{1,3}bbc
,目标字符串为"abbbc",则回溯就会有很多:
再看看".*"
是如何匹配"abc"de
的:
可以看到,由于正则是贪婪的,所以.*会尽可能地往前走,看到不行就回溯,直到可以匹配为止。
而对于非贪婪,也会存在回溯,如^(\d{1,3}?)(\d{1,3})$
匹配12345
:
比如".*?"
匹配123"abc"456
。
2.2 减少回溯的方法
再举个例子,正则表达式为".*"
,字符串为The name "McDonald's" is said "makudonarudo" in Japanese
,可以看到很顺利地匹配成功了。(阴影部分是POSIX NFA,但现在我们讨论的是传统型NFA,所以到D就匹配成功了)
但如果匹配失败呢?就会多了很多次回溯的尝试。
现在我们其实是想匹配引号里面的内容,也就是说引号里面可以没有引号,那么我们就可以换个正则:"[^"]*"!
,可以看到回溯明显比上面的少(注意,这两个正则意义不一样,使用时务必需要分清楚)
2.3 调整多选结构顺序
现在我们需要匹配引号里面的字符(包括转义的引号),如字符串为"2 \"x3\" likeness"
,我们需要提取2 \"x3\" likeness
,那么有以下的方案:
可以看到,[^"\\]
放到\\.
前面可以有效减少回溯次数。
但值得注意的是,这种改动并不能加快报告失败的速度,因为在报告匹配失败之前,所有可能的匹配都已经被尝试。
2.4 避免量词嵌套
这篇文章提到,(.+)*
这种量词结合结构造成的回溯是指数级别的,因为+
会对字符串做任意长度的切割,而*
则会在切割的基础上进行多次迭代,一旦不匹配字符串,对于长度为n的字符串,尝试次数为2^{n+2}+2^{n+1}-k
(具体数字可能用不同的编译方式、不同的字符串会不一样,但只需知道是指数级别即可)。
举例:用(a+)*b
去匹配aaaaaaaaaa
(10个a),很明显会匹配失败,那么尝试的次数为:2^12+2^11-3=6141
加多一个a,次数为2^13+2^12-3=12285
20个a,次数为749997(这个不适用于公式了,我也不知道为啥,但真的是指数级别)
总之,不要用量词嵌套,(.*)*和(.+)*
都不要用,下面的效果是一样的
(a*)* <==> (a+)* <==> (a*)+ <==> a*
(a+)+ <==> a+
2.5 一些提高正则效率的操作
除了上面三条之外,以下操作也有助于正则效率的提升:
(1) 用具体化字符组替代通配符
".*"
可以替换为"[^"]*"
(2) 调整多选结构顺序
尽可能把命中几率高的放在多选结构前面
(3) 不要用量词嵌套
(a*)* <==> (a+)* <==> (a*)+ <==> a*
(a+)+ <==> a+
(4) 使用非捕获性分组
有时候难免会用到()
,如果不想捕获括号里面的内容,请用(?:)
替代()
。
(5) 独立出确定字符
如a+
可以替换成aa*
,这样可以加快判断是否匹配失败,进而加快移位的速度。
(6) 提取分支公共部分
如this|that
可以修改为th(?:is|at)
,这样th
就只需要检查一遍。
(7) 减少分支数量,缩小分支范围
如red|read
修改成rea?d
(8) 消除不必要的字符组
如[.]
修改成\.
(9) 用字符组代替多选结构
用[uvwxyz]
代替u|v|w|x|y|z
,因为前者只进行匹配操作,而后者可能需要在目标字符串的每个位置进行6次回溯。
四. 正则在Xcode的使用
1. 使用正则查找特定字符
先想象一下,现在我们面对的是一个几千行的文件,因为debug需要,我们需要找到一个property:normal
的被赋值过程,按平常的思维,你肯定会想到 command+F
->搜索normal
,顶多再勾选一下Case Sensitive
和Matches word
,然后疯狂按回车去找。
当然上面的方法不是说没问题,但如果这个文件很多带有normal
的方法和normal
的临时变量,再加上我们现在需要找的是被赋值的过程(setter),找到的可能很多都是调用该normal变量
的方法,可能你找一趟之后,前面的又忘了。
你可能又会想到解决方法,在搜索界面搜索normal =
,这个看上去还行,但是鉴于有些人的代码风格可能不太一样,你不知道变量和等号之中隔了几个空格,比如有些人有强迫症:
self.normal = 1
self.longlongParam = 2;
self.longlonglonglonglonglonglongParam = 3;
又或者有些不符合代码规范的赋值方法:
self.normal=1;
self.longlongParam = 2;
这时候你的查找方法就可能会漏掉一些情况了,而正则表达式,无疑给模糊搜索提供了一个有力的帮助,再想一想我们要找的property的特征:self.normal
或者_normal
,后面接上若干个空格和一个等号。
因此可以得到正则表达式(self\.|\_)normal\s*=
,再勾选一下Regular Expression
就可以了,或者如果你想在左侧搜索栏里面一一列举出来,你也可以右键该文件所在的文件夹,选Find in Selected Groups...
,就可以更直观地找到该property的所有赋值过程,还可以一一点进去看。
2. 使用正则替换字符
案例一:给系统方法加一个参数
有时候,我们在模糊匹配之后,还需要替换或者复用这些字符,这时候怎么办呢?用()
匹配到子字符串,再用$
符号表示匹配到的字符串就可以了。
这篇文章举了一个需要在工程中全局使用正则替换的例子:在iOS6之前的系统方法是presentModalViewController:animated:
,而在iOS6之后,该方法已过期,系统方法变为presentModalViewController:animated:completion:
,如果想批量加一个参数completion:nil
怎么办呢?
原文提到用这个正则表达式:presentModalViewController:(.*) animated:(.*)\]
找到所有的方法,然后用presentViewController:$1 animated:$2 completion:nil]
但有个问题就是,如果这个方法是带有换行符号的话会匹配不到:
// 可以匹配
[self presentModalViewController:vc animated:YES];
// 匹配不到
[self presentModalViewController:vc
animated:YES];
所以考虑到换行的情况,我们用presentModalViewController:(\w*)(\s*)animated:(.*)\]
匹配方法,然后用presentModalViewController:$1$2animated:$3$2completion:nil\]
去替换,这样就能在保留原来格式的基础上,增加一个方法参数了。
// 替换结果1
[self presentModalViewController:vc animated:YES completion:nil];
// 替换结果2
[self presentModalViewController:vc
animated:YES
completion:nil];
案例二:批量为#define文件新增方法
再来一个例子,这篇文章则提到了接口字符串的替换:
一开始的define长这样:
#define host @"https://xxx"
#define login @"/login"
#define register @"/register"
后面觉得自己每次都要手动拼接就很烦,索性自己在define阶段就把他拼接好完事了,类似实现这样的效果:
#define host @"https://xxx"
#define login [host stringByAppendingString:@"/login"]
#define register [host stringByAppendingString:@"/register"]
如果手动搞,最烦的地方就是,你首先要用鼠标点击到@前面,粘贴一下,然后又点击到最右边,按一下],当然参数少的时候也不是很花时间,但是如果是一个上百行的文件的话,那就很酸爽了。。
现在我们假定需要整理的是符合格式为#define xxx @"/xxx"
的定义,并为他自动加上拼接代码,需要考虑到的情况有:
// url
#define host @"https://xxx"
// define和xxx之间有若干个空格
#define login @"/login"
// xxx和@"/xxx"之间有若干个空格
#define register @"/register"
// 中间可能还有"\"和换行符
#define \
forget \
@"/forget"
我们要用到的正则是#define([\\\s]*)(.*)([\\\s]*)@"(/.*)"
,替换规则为#define$1$2$3[host stringByAppendingString:@"$4"]
,这样我们就可以替换到了:
// url
#define host @"https://xxx"
// define和xxx之间有若干个空格
#define login [host stringByAppendingString:@"/login"]
// xxx和@"/xxx"之间有若干个空格
#define register [host stringByAppendingString:@"/register"]
// 中间可能还有"\"和换行符
#define \
forget \
[host stringByAppendingString:@"/forget"]
在替换的时候,尽量要想到多空格、换行的情况,并且尽可能保证原有格式不被破坏,替换之后回头检查一下每一个修改,这样有利于维护较大文件的可读性(因为你替换的时候有可能一次性替换了上百条)
五. iOS内部工具实战
正则除了在开发层面帮助我们提升效率之外,还可以给我们创造优化工程的可能性,下面我将讲解两个核心功能用到正则的内部工具。
1. 使用正则表达式检测工程内无用的图片资源
这个工具的核心思想是:对于一个资源的名字,如果这个名字在特定类型的文件中被特定格式引用,如:.h、.m文件就是@"xxx"
,.xib文件就是@"image name="xxx"
等等,那么这个图片资源被视为有用的,否则,就会被视为无用资源,可以被移除。
由此可以得到正则表达式@"(.*?)"
和image name="(.+?)"
去匹配这些文件。
对于带数字的图片,我们就不能简单地这样判断了,因为工程中引用一般都是这样的:[NSString stringWithFormat:@"xxx_%ld", index]
,而工程名通常是这样的:xxx_1
、xxx_2
...
这时候如果还按照全匹配的思路,那么这些带数字的文件就会被误删掉了,所以,我们通常会取数字的前缀xxx_
,然后根据工程中是否含有xxx_%
或者xxx_[0-9]
去判断。
有些奇怪的图片把序号放前面,1_xxx
,所以我们还需要判断是取前缀or后缀,对于前缀要用正则去搞:%(.*?)xxx
,这样就可以覆盖到带数字的图片了。
效果也是比较可观的(我错过了两个版本才去截图,又多了一M多的大小= =,本来应该删除了6M多的)
具体实现和实践可以看我之前写的iOS瘦身——移除无用资源的LSUnusedResources源码分析与优化
2. 使用正则表达式检测分类同名方法
我们知道,由于iOS语言的特性,object setup阶段,会把category方法插入到类的方法列表中,如果分类中有和原有类同名的方法, 会优先调用分类中的方法, 就是说会忽略原有类的方法,可能会导致原有功能不可用。
所以我写了个脚本,用于检测有没有分类覆盖原有方法,并在编译时自动运行,就可以避免意外情况的发生了。
这个脚本的关键之处就在于:如何准确地提取所有类的方法,并把它们联系起来。因此获得思路如下:
第一步:需要先用@implementation(.*?)@end
提取到文件的内容和类名。
第二步:匹配到所有的类方法或者实例方法,以+或者-开头,且以{结束,中间可能有若干个括号、空格、换行的若干情况的一段文本:
(\+|\-)\s*\([^;<>=\+\-]*?\)\s*([^;<>=\+\-]*?)\s*\{
上面的正则即可匹配到-(xxx)func:(xxx)param func:(xxx)param ... {}
类似的格式,且把一些可能引起混淆的运算字符踢了出去,以避免加减号干扰到正则了。(注意:+ load方法要排除在外)。
第三步:匹配出所有带参数的前缀,用下面的正则表达式,如果不匹配,则说明这个方法没有参数,如果匹配了,则取每一个匹配的参数进行拼接即可。
(\w*?)\s*:\s*\(.*?\)
对于库文件则用nm -j
指令提取符号表,再处理相应的方法就可以了,具体实现和实践可以看我的iOS分类 同名方法自动检测脚本。
效果是找到了工程中10处被覆盖的分类方法,16个迁移时未被及时移除的工程文件,以及6个已经失效的无用文件。在以后的开发过程中,这种不符合规范的写法就会被扼杀在开发阶段了。
3. 使用正则表达式检测监听未移除情况
在iOS开发过程中,如果增加了KVO和通知,需要在销毁时候及时移除掉,不然的话就有可能会崩溃。为了避免同事们开发过程中忘记remove,需要写个脚本,在编译时自动监测是否有移除,将崩溃扼杀于开发阶段中。
3.1 通知的监听移除情况
通知的通用创建为:[[NSNotificationCenter defaultCenter] addObserver:(weak)self(.param) selector:...]
,由于工程中加了个宏NOTIFICATION_CENTER
,因此需要检测以上两种格式,得正则:
(\[NSNotificationCenter\s*defaultCenter\]|NOTIFICATION_CENTER)\s*?addObserver:\s*?.*?[sS]elf(.*?)\s+?selector:
通知的通用移除为:[[NSNotificationCenter defaultCenter] removeObserver:(weak)self(.param) ....]
,得正则:
observer = '.*?[sS]elf' + param # 这里的param是指上面添加方法的参数
(\[NSNotificationCenter\s*?defaultCenter\]|NOTIFICATION_CENTER)\s*?removeObserver:\s*? + observer + r'(\s*name:.*?)?(\s*object:.*?)?\s*?\]'
如果创建能匹配,移除不能匹配,则视为没有被移除,从而报错。
3.2 KVO的监听移除情况
KVO的通用创建为:[xxx addObserver:(weak)self(.param) forKeyPath:xxx options:.....]
,则可以通过以下正则检测:
addObserver:\s*.*?[sS]elf(.*?)\s+?forKeyPath:\s*(.+?)\s+?options:
KVO的通用移除为:[xxx removeObserver:(weak)self(.param) forKeyPath:....]
,则可以通过以下正则检测,其中param和keypath为上述正则的匹配结果:
observer = '.*?[sS]elf' + param
'removeObserver:\s*' + observer + '\s+?forKeyPath:\s*' + keypath
如果创建能匹配,移除不能匹配,则视为没有被移除,从而报错。测试样例可以看iOS通知/KVO移除 自动检测脚本
六. 总结
这篇文章主要介绍了正则的入门级语法和一些实践,在日常开发过程中,如果涉及到模糊匹配的,应第一时间想到用正则,他是一个一行顶几十行的神器,能够大幅度提升工作效率!