公司项目用到一个三方开源库,里面有个bug,不能改动源码,我想来想去,只能通过runtime这个万能的手段来解决。但是runtime 并不怎么会用,怎么办,马上学习呗。说到runtime,它是Objective-C里面最核心的技术,被人们传呼的神乎其神,但是感觉有一层神秘的面纱笼罩其上,毕竟使用场景不多,相信大多数开发者都不会熟练的运用。而网络上也有无数的文章来讲解runtime,但是真的非常的乱,非常的碎片化,很少有讲解的比较全面的。
最初是在onevcat的博客上看到runtime的runtime的博客,说句实话,看完后我还是蒙的,这里面主要讲了一下runtime 比较核心的功能-Method Swizzling,不过看完后还是有些不知如何下手的感觉。下面是我自己对runtime的整理,从零开始,由浅入深,并且带了几个runtime实际的应用场景。看完之后,你可以再回过头来看喵神的这篇文章,应该就能看的懂了。
一:基本概念
Runtime基本是用C和汇编写的,可见苹果为了动态系统的高效而作出的努力。你可以在这里下到苹果维护的开源代码。苹果和GNU各自维护一个开源的runtime版本,这两个版本之间都在努力的保持一致。Objective-C 从三种不同的层级上与 Runtime 系统进行交互,分别是通过 Objective-C 源代码,通过 Foundation 框架的NSObject类定义的方法,通过对 runtime 函数的直接调用。大部分情况下你就只管写你的Objc代码就行,runtime 系统自动在幕后辛勤劳作着。
- RunTime简称运行时,就是系统在运行的时候的一些机制,其中最主要的是消息机制。
- 对于C语言,函数的调用在编译的时候会决定调用哪个函数,编译完成之后直接顺序执行,无任何二义性。
- OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。而C语言在编译阶段就会报错)。
- 只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。
二:runtime的具体实现
我们写的oc代码,它在运行的时候也是转换成了runtime方式运行的,更好的理解runtime,也能帮我们更深的掌握oc语言。
每一个oc的方法,底层必然有一个与之对应的runtime方法。
- 当我们用OC写下这样一段代码
[tableView cellForRowAtIndexPath:indexPath];
- 在编译时RunTime会将上述代码转化成[发送消息]
objc_msgSend(tableView, @selector(cellForRowAtIndexPath:),indexPath);
三:常见方法
unsigned int count;
- 获取属性列表
123objc_property_t *propertyList = class_copyPropertyList([self class], &count);for (unsigned int i=0; i%@", [NSString stringWithUTF8String:propertyName]);} - 获取方法列表
123Method *methodList = class_copyMethodList([self class], &count);for (unsigned int i; i%@", NSStringFromSelector(method_getName(method)));}
- 获取成员变量列表
123Ivar *ivarList = class_copyIvarList([self class], &count);for (unsigned int i; i%@", [NSString stringWithUTF8String:ivarName]);} - 获取协议列表
123__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);for (unsigned int i; i%@", [NSString stringWithUTF8String:protocolName]);}
现在有一个Person类,和person创建的xiaoming对象,有test1和test2两个方法
- 获得类方法
1 2 3 |
Class PersonClass = object_getClass([Person class]); SEL oriSEL = @selector(test1); Method oriMethod = class_getInstanceMethod(xiaomingClass, oriSEL); |
- 获得实例方法
1 2 3 |
Class PersonClass = object_getClass([xiaoming class]); SEL oriSEL = @selector(test2); Method cusMethod = class_getInstanceMethod(xiaomingClass, oriSEL); |
- 添加方法
1 |
BOOL addSucc = class_addMethod(xiaomingClass, oriSEL, method_getImplementation(cusMethod), method_getTypeEncoding(cusMethod)); |
- 替换原方法实现
1 |
class_replaceMethod(toolClass, cusSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod)); |
- 交换两个方法
1 |
method_exchangeImplementations(oriMethod, cusMethod); |
四:常见作用
- 动态的添加对象的成员变量和方法
- 动态交换两个方法的实现
- 拦截并替换方法
- 在方法上增加额外功能
- 实现NSCoding的自动归档和解档
- 实现字典转模型的自动转换
五:代码实现
要使用runtime,要先引入头文件#import
这些代码的实例有浅入深逐步讲解,最后附上一个我在公司项目中遇到的一个实际问题。
1. 动态变量控制
在程序中,xiaoming的age是10,后来被runtime变成了20,来看看runtime是怎么做到的。
1.动态获取XiaoMing类中的所有属性[当然包括私有]
Ivar *ivar = class_copyIvarList([self.xiaoming class], &count);
2.遍历属性找到对应name字段
const char *varName = ivar_getName(var);
3.修改对应的字段值成20
object_setIvar(self.xiaoMing, var, @"20");
4.代码参考
1 2 3 4 |
-(void)answer{ unsigned int count = 0; Ivar *ivar = class_copyIvarList([self.xiaoMing class], &count); for (int i = 0; i |
2.动态添加方法
在程序当中,假设XiaoMing的中没有guess
这个方法,后来被Runtime添加一个名字叫guess的方法,最终再调用guess方法做出相应。那么,Runtime是如何做到的呢?
1.动态给XiaoMing类中添加guess方法:
1 |
class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:"); |
这里参数地方说明一下:
(IMP)guessAnswer 意思是guessAnswer的地址指针;
“v@:” 意思是,v代表无返回值void,如果是i则代表int;@代表 id sel; : 代表 SEL _cmd;
“v@:@@” 意思是,两个参数的没有返回值。
2.调用guess方法响应事件:
[self.xiaoMing performSelector:@selector(guess)];
3.编写guessAnswer的实现:
void guessAnswer(id self,SEL _cmd){
NSLog(@”i am from beijing”);
}
这个有两个地方留意一下:
- void的前面没有+、-号,因为只是C的代码。
- 必须有两个指定参数(id self,SEL _cmd)
4.代码参考
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
-(void)answer{ class_addMethod([self.xiaoMing class], @selector(guess), (IMP)guessAnswer, "v@:"); if ([self.xiaoMing respondsToSelector:@selector(guess)]) { [self.xiaoMing performSelector:@selector(guess)]; } else{ NSLog(@"Sorry,I don't know"); } } void guessAnswer(id self,SEL _cmd){ NSLog(@"i am from beijing"); } |
3:动态交换两个方法的实现
在程序当中,假设XiaoMing的中有test1
和 test2
这两个方法,后来被Runtime交换方法后,每次调动test1
的时候就会去执行test2
,调动test2
的时候就会去执行test1
, 。那么,Runtime是如何做到的呢?
- 获取这个类中的两个方法并交换
1 2 3 |
Method m1 = class_getInstanceMethod([self.xiaoMing class], @selector(test1)); Method m2 = class_getInstanceMethod([self.xiaoMing class], @selector(test2)); method_exchangeImplementations(m1, m2); |
交换方法之后,以后每次调用这两个方法都会交换方法的实现
4:拦截并替换方法
在程序当中,假设XiaoMing的中有test1
这个方法,但是由于某种原因,我们要改变这个方法的实现,但是又不能去动它的源代码(正如一些开源库出现问题的时候),这个时候runtime就派上用场了。
我们先增加一个tool类,然后写一个我们自己实现的方法-change,
通过runtime把test1替换成change。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |