作者Talent•C
转载请注明出处
前言
这几天暂时比较闲,翻翻博客看一下,发现一年没有更新了,很惭愧啊~~
说一个工程中有一个很有意思的代码现象吧,使用 Runtime
中 method_exchangeImplementations 函数将 UIViewController
的 viewDidLoad 函数 与 myViewDidLoad 函数交换后在其内部调用 [self myViewDidLoad];,看到这个代码难道不会出现递归调用吗? 带这个这个疑问我们来看一下其原因吧。
代码示例:
Method Swizzling原理
Method Swizzing
是发生在运行时的,主要用于在运行时将两个 Method 进行交换,我们可以将 Method Swizzling 代码写到任何地方,但是只有在这段 Method Swilzzling 代码执行完毕之后互换才起作用。
而且 Method Swizzling 也是iOS
中AOP
(面相切面编程)的一种实现方式,我们可以利用苹果这一特性来实现AOP
编程。
首先,让我们通过两张图片来了解一下 Method Swizzling 的实现原理
未做方法交换:
方法交换后:
在OC
语言的runtime
特性中,调用一个对象的方法就是给这个对象发送消息。是通过查找接收消息对象的方法列表,从方法列表中查找对应的SEL
,这个SEL
对应着一个IMP
(一个IMP
可以对应多个SEL
),通过这个IMP
找到对应的方法调用。
在每个类中都有一个 Dispatch Table,这个 Dispatch Table 本质是将类中的SEL
和IMP
(可以理解为函数指针)进行对应。而我们的 Method Swizzling 就是对这个 table 进行了操作,让SEL
对应另一个IMP
。
Method Swizzling使用
在实现 Method Swizzling 时,核心代码主要就是一个runtime
的C语言API
:
了解完原理后我们具体操作一下 创建一个iOS
工程,并给 UIViewController 添加一个Category
,然后在Category
中的 +(void)load 方法中添加 Method Swizzling 方法,我们用来替换的方法也写在这个Category
中。由于 +load 类方法是程序运行时这个类被加载到内存中就调用的一个方法,执行比较早,并且不需要我们手动调用。而且这个方法具有唯一性,也就是只会被调用一次,不用担心资源抢夺的问题。
代码如下:
看到这里就出现了文章开头的问题了, myViewDidLoad 中调用自己会不会出现递归调用???
答案是当然不会的, 通过上面两个图就可以看出原因来,Method Swizzling 的实现原理可以理解为 ”方法互换“。假设我们将A和B两个方法进行互换,向A方法发送消息时执行的却是B方法,向B方法发送消息时执行的是A方法。
例如我们上面的代码,系统调用 UIViewController 的 viewDidLoad 方法时,实际上执行的是我们实现的 myViewDidLoad 方法。而我们在 myViewDidLoad 方法内部调用 [self myViewDidLoad]; 时,执行的是 UIViewController的 viewDidLoad 方法。所以并不会出现递归调用。
我们将上面的 myViewDidLoad 函数实现修改一下,按照上面的解释应该就会出现递归调用,我们来验证一下:
运行程序会发现程序启动后卡住了,几秒后程序崩溃了,在调用堆栈里看到出现了方法递归调用。
关于 Method Swizzling 的用处有很多,这里给大家推荐一个Github上星星最多的一个第三方库-JRSwizzle
总结
天下难事,必做于易;天下大事,必做于细。 —— 老子