运行时是iOS中一个很重要的概念,iOS运行过程中都会被转化为runtime的C代码执行。例如[target doSomething];会被转化成objc)msgSend(target,@selector(doSomething))来执行。这篇博客会较为全面的来讲解下Runtime。
OC是一门动态语言,它将很多静态语言在编译和链接时做的事放到了运行时来处理。这种动态语言的优势在于:写代码能更加灵活,可以把消息转发给想要的对象,或者随意交换一个方法的实现。
OC Runtime目前有两个版本:Modern Runtime和Legacy Runtime。Modern Runtime覆盖了64位的App,Legacy Runtime使用早期的32位App,所以现在可以不用管了。
(1)当我们需要使用Runtime的接口时,需要导入头文件:#import <objc/runtime.h>,Runtime可以进行如下操作,在运行时来获取当前类中的一些信息:
获取属性列表:
。
获取方法列表:
。
获取成员变量列表:
。
获取协议列表:
。
所以,我们大概可以总结出Runtime的作用:
- 程序运行中,动态创建一个类;
- 程序运行中,动态为某个类添加属性/方法,修改属性/方法;
- 遍历一个类的所有成员变量/属性/方法;
(2)方法调用:
如果用实例对象调用实例方法,会到实例的isa指针指向的对象(也就是类对象,没错,其实类也是一个对象)操作。如果调用的是类方法,就会到类的isa指针指向的对象(元类对象)中操作。查找过程如下:
- 首先在相应操作的对象中的缓存方法列表中找调用的方法,如果找到,转向相应实现并执行;
- 如果没有找到,在相应操作的对象中的方法列表中找调用的方法,如果找到,转向相应实现执行;
- 如果没找到,去父类指针所指向的对象中执行以上步骤;
- 以此类推,如果一直到根类还没有找到,转向拦截调用;
- 如果没有重写拦截调用的方法,程序报错;
说明下,重写父类的方法,其实并没有覆盖掉父类的方法,只是在当前类对象中找到这个方法后就不会再去父类中找了。如果想调用已经重写过的方法的父类实现,只需使用super这个编译器标志,它会在运行时跳过在当前类对象中寻找方法的过程。
(3)拦截调用
拦截调用就是在找不到调用的方法程序崩溃之前,有机会通过重写NSObject的四个方法来处理:
。
以下两个方法需要转发到其他类处理:
。
- 第一个方法是调用一个不存在的类方法的时候,会调用这个方法,默认返回NO,可以加上自己的处理后返回YES;
- 第二个方法和第一个方法类似,处理的是实例方法;
- 第三个方法是将你调用的不存在的方法重定向到一个其他声明了这个方法的类,只需要返回一个有这个方法的target;
- 第四个方法是将你调用的不存在的方法打包成NSInvocation传给你。做完你自己的处理后,调用invokeWithTarget:方法让某个target触发这个方法;
- Class cls :给哪个类添加方法;
- SEL name:方法选择器selector;
- IMP imp:方法的实现,C方法的实现可以直接获得。如果是OC方法,可以用+(IMP)instanceMethodForSelector获得方法的实现;
- const char *types:方法的签名
- id object:给谁设置关联对象;
- const void *key:关联对象唯一的key,就是上面定义的全局变量;
- id value:关联对象;
- objc_AssociatedPolicy:关联策略:
- id object:获取谁的关联对象;
- const void *key:根据这个唯一的key获取关联对象;