iOS Method Swizzle黑魔法小记
本文主要内容为用runtime实现Swizzle,即调换两个方法的实现
一点iOS runtime的基本知识
Objective-C是一门动态语言,每个方法在运行时会被动态转为消息发送,方法的实现由运行时决定
[foo message:@"ss"] 向foo对象发送message的消息会被runtime转换成
((void*)(id,SEL,id))(objc_msgSend)(foo,@selector(message),@"ss")
当然在代码里上面这两个方法都是等价的,实现效果都一样
objc_msgSends是一个类型为((void*)(id,SEL,id))的函数指针。
下面说说objc_msgSends的机制
看看Objective-C中类
和对象
的定义
类定义
如上面[foo message:@"ss"] 中的foo对象是objc_object结构类型的,他里面有一个isa指针指向他的类对象Class,所以foo对象的所有信息都可以在Class里面找到,如实例对象,方法列表等
当执行[foo message:@"ss"]的时候
- [foo message:@"ss"]转换为
((void*)(id,SEL,id))(objc_msgSend)(foo,@selector(message),@"ss")
- 以上述[foo message:@"ss"]为例子,runtime会根据foo对象的isa指针找到该对象所属的类,然后以@selector(message)为键在该对象的方法列表(methodLists)寻找方法运行,若最终找不到方法,runtime提供3次机会将方法进行处理,否则最后会抛出unrecognized selector sent to XXX的异常
- 三次补救机会看Objective-C Runtime 1小时入门教程
本文的Swizzle黑魔法从上面第二点入手,替换methodLists中的方法
本文例子,替换UIViewcontroller的viewdidLoad方法
新建SwizzleHelper类
- 首先用
class_getInstanceMethod
获取UIViewController的ViewdidLoad
方法和SwizzleHelper的
jm_viewDidLoad
方法
Mehod是一个结构体,如下
- SEL method_name是方法的名字
- IMP mehod_imp是一个函数指针,对应方法的实现
(可以理解为方法{}里面的内容)
,下面将会替换这个IMP来实现方法替换
- 用
class_addMethod
向UIViewController类中添加jm_viewDidLoad
方法 - 再获取添加后UIViewController中的
jm_viewDidLoad
方法 - 用
method_exchangeImplementations
将UIViewController中的ViewdidLoad
方法和jm_viewDidLoad
的实现进行替换
运行后打印,证明方法已被替换
屏幕快照 2016-09-30 下午6.07.45.png再说说下面这个替换的方法
屏幕快照 2016-09-30 下午6.14.04.png
为什么再调用一次呢,因为jm_viewDidLoad方法的实现已被替换,jm_viewDidLoad对应的是原viewdidLoad方法的实现。
替换后调用过程
当UIViewController对象调用viewDidLoad的时候,会转成((void*)(id,SEL,id))(objc_msgSend)((UIViewcontroller对象),@selector(viewDidLoad))
,此时runtime会根据UIViewcontroller对象的isa指针找到该对象所属的类,然后以@selector(viewDidLoad)为键在该对象的方法列表 (methodLists)寻找方法运行,此时viewDidLoad方法的实现已经替换为jm_viewDidLoad的实现方法,从而实现了方法替换.
思考1
屏幕快照 2016-09-30 下午7.16.44.png尝试不将jm_viewDidLoad方法加入UIViewcontroller类里面,然后直接替换jm_viewDidLoad和viewdidLoad方法的实现,却出现下面异常。
屏幕快照 2016-09-30 下午7.21.32.png为什么呢?
因为虽然实现替换成功了,但由于jm_viewDidLoad里面又调用了一次[self jm_viewDidLoad],又由于UIViewcontroller中没有添加jm_viewDidLoad方法,所以调用[self jm_viewDidLoad]时会找不到方法从而抛出异常。
思考2
为什么在+(void)load方法中进行替换呢?
+(void)load方法在main()前类载入内存时会调用一次。详细看
NSObject的load和initialize方法
你真的了解 Objective-C 中的load 方法么?
Demo下载
参考
iOS class_addMethod使用
说说objcRuntime的一些妙用(class_addMethod,class_replaceMethod)
iOS开发:Runtime黑魔法,替换系统类方法及属性
Objective-C Runtime 1小时入门教程