什么是Runtime
Objective-C语言是一门动态语言,就是尽可能地把决定从编译器推迟到运行期, 就是尽可能地做到动态. 只是在运行的时候才会去确定对象的类型和方法的. 因此利用Runtime机制可以在程序运行时动态地修改类和对象中的所有属性和方法.
Runtime是OC底层的一套C语言的Api,编译器最终都会将OC代码转化为运行时的代码。通过终端命令编译.m文件:$clang -rewrite-objc xxx.m可以看到编译后的xxx.cpp(C++文件)
例如创建一个对象[[NSObject alloc]init],最终被转换为几万行代码,截取关键的一句可以看到底层是如何通过runtime创建的对象。
1 | int main(int argc, const char * argv[]) { |
删除一些强制转换的语句,可以看到调用方法本质就是发消息,[[NSObject allock]init]语句发送了两次消息,第一次发了alloc消息,第二次发送init消息。
1 | id obj = objc_msgSend(objc_getClass("NSObject"),sel_registerName("alloc")) |
另外利用runtime可以做一些OC不容易实现的功能
- 动态交换两个方法的实现(特别是交换系统自带的方法)
- 动态添加对象的成员变量和成员方法
- 获得某个类的所有成员方法、所有成员变量
如何应用运行时?
- 将某些OC代码转化为运行时代码,探究底层,比如block的实现原理;
- 拦截系统自带的方法调用(Swizzle 黑魔法),比如拦截imageNamed、viewDidload、alloc;
- 实现分类添加属性;
- 实现NSCoding的自动归档和自动解档;
- 实现字典和模型的自动转换;
实例
一、交换两个方法的实现,拦截系统自带方法的调用
需要用到的库
<objc/runtime.h>
- 获得某个类的类方法
1
Method class_getClassMethod(Class cls , SEL name)
- 获得某个类的实例对象方法
1
Method class_getInstanceMethod(Class cls , SEL name)
- 交换两个方法的实现
1
void method_exchangeImplementations(Method m1 , Method m2)
案例1:方法简单的交换
创建一个Person类,类中实现以下两个类方法,并在.h文件中声明
1 | + (void)run { |
运行
1 | [Person run]; |
下面通过runtime实现方法的交换,类方法class_getClassMethod对象方法用class_getInstanceMethod
1 | // 获取两个类的类方法 |
案例2:拦截系统方法
需求:所有对象创建时进行打印,如何不自定义初始化方法实现打印呢?
步骤:
1、 为NSObject创建一个分类(NSObject+Category)
2、 在分类中实现一个自定义方法,方法中打印对象创建的语句
1 | - (instancetype)hk_init { |
3、分类中重写NSObjct的load方法,实现方法的交换
1 | //获取类的实例方法 |
运行
1 | Person *p = [[Person alloc] init]; |
二、在分类中设置属性,给任何一个对象设置属性
众所周知,分类中是无法添加属性的。但是如果确实有这个需求就需要用到runtime为分类添加属性
需要用到的库
<objc/runtime.h>
- set方法
1 | /** |
- get方法
1 | /** |
步骤:
1、创建一个分类,比如给任何一个对象都添加一个name属性,就是NSObject添加分类(NSObject+Category)
2、先在.h中@property声明get和set方法,方便点语法调用
1 | @property(nonatomic,copy)NSString *name; |
3、在.m中重写set和get方法,内部利用runtime给属性赋值和取值
1 | char nameKey; |
运行
1 | Person *p = [[Person alloc] init]; |
三、获得一个类的所有成员变量
最典型的用法就是一个对象在归档和解档的encodeWithCoder和initWithCoder:方法中需要该对象所有的属性进行decodeObjectForKey:和encodeObject:,通过runtime我们声明中无论写多少个属性,都不需要再修改实现中的代码了。
需要用到的库
<objc/runtime.h>
获得某个类的所有成员变量(outCount会返回成员变量的总数)
参数- cls:那个类
- outCount:放一个接收值的地址,用来存放属性的个数
- ivar:存放所有获取到的属性
1 | Ivar *ivars = class_copyIvarList(Class cls , unsigned int *outCount) |
- 获取成员变量的名字
1 | const char *ivar_getName(Ivar v) |
- 获得成员变量的类型
1 | const char *ivar_getTypeEndcoding(Ivar v) |
案例1:获取Person类中所有成员变量的名字和类型
1 | unsigned int outCount = 0; |
案例2:利用runtime获取所有属性来重写归档解档方法
1 | //归档 |
同理用以上方法可以直接对NSObject做一个分类,让所有对象都具有归档能力
案例3:利用runtime获取所有属性来进行字典转模型
字典转模型我们需要考虑三种特殊情况:
1.当字典的key和模型的属性匹配不上
2.模型中嵌套模型
3.数组包含模型
举例说明:
1 | { |
NSObject+JSONExtention.h
1
2 // 返回数组中都是什么类型的模型对象
- (NSString *)arrayObjectClass ;
此方法需对应的模型实现,返回数组模型的类名NSObject+JSONExtention.m
1 |
|