最近在研究method swizzing,读到了这一偏文章,原文链接,这篇链接已经说得很清楚了,网上80%的关于对viewWillAppear这个方法的交换的搜索结果,都是在viewWillAppear已经存在的情况下进行的。但是如果想要交换的是一个未实现的方法又是怎么样的呢,链接里面文章说的很清楚,在这里记录一下。
先介绍一下会使用到的几个函数,对这些函数了解的可以直接跳过
1:class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) ,给cls类添加一个name方法的实现,在调用cls的name方法的时候,会转为调用imp方法,type是参数和返回值的类型。
用法如下:
有测试类MethodTest,不包含任何类方法或者实例方法,现在想要给他添加一个pringLog的实例方法,如下
使用class_addMethod来添加实例方法,即想MethodTest添加一个printLog的方法,调用这个方法的时候,会转为调用self的实例方法addMethodLog,没有参数,由于MethodTest没有pringLog方法,要使用运行时的方法performSelector来调用,避免编译出错。
最终的运行结果
,确实调用了self 里面的addMethodLog方法。
2: class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp,const char * _Nullable types) 方法替换:将cls里面的那么方法 替换成Imp指针指向的方法,参数和返回值是types.
在刚才的MethodTest文件里面添加两个实例方法如下

打印结果如下:
其实像这样的已经实现的方法,通过 method_exchangeImplementations 也能达到效果。
进入正题
先上测试代码,
分析一下这一段代码:
1):53,54,55,56这四行代码就是获得了两个Sel选择子,两个Method对象,值得注意的是,在当前这个类中,并没有testThhree这个已经实现的实例方法,只有testTwo的实例方法,所以在debug的时候,可以发现这个TestOne是为nil的。这个就是我们今天讨论的话题,没有实现的实例方法的问题
2):57行,向当前的这个类添加一个oneSelector的方法,调用oneSeletort的时候,会转为调用当前类的testTwo方法,这个就是上面class_addMethod方法的用法,不做重复说明,返回的didAddMethod为Yes,表示当前的self里面没有实现testThree方法,动态添加方法成功,也就是说再调用当前类的testThree方法的时候,会转为调用testTwo方法。
3):58行,didAddMethod表示成功,在执行class_replace方法来交换两个方法的时候,本来是想达到方法交换的效果,但是由于testOne是为nil的,所以交换并没有成功,我的理解是,这个时候testThree会调用testTwo,因为交换成功;但是testTwo交换失败,所以还是会调用testTwo
4):63行,测试调用testThree,为了编译不错误,只能使用这种方式调用,按照上面的分析,testThree会调用66行的testTwo,会输出2222222,然后68行又调用了testTwo,由于交换失败(失败的原因就是testThree没有实现),testTwo还是调用testTwo,导致了死循环
运行结果如下,死循环验证了我们的结果。
所以这个结果就是说明,网上那些已viewWillAppear方法来举例子的是存在漏洞的,根本的问题就是testThree没有实现,给testTwo换一个实现就可以了,如下
换一个已经存在的testResult的实现就可以了,打印结果:
其实要是都实现了,直接交换不就完了,这个只是为了指出网上那80%的漏洞而已。
最后记一点笔记
Sel:选择子,就是selector返回的,在同一个类对象里面是唯一的,表示要调用某一个方法.
Imp:是一个指针,指向要调用的方法的首地址,可以理解为选择子Sel是键,通过键可以找到方法的指针,指向要执行的方法的首地址。
Method,对上面两个的包装吧,就是方法。