一,什么是Runtime?
runtime就是运行时,OC是运行时语言,指不是编译的时候决定调用谁,而是运行到那的时候才决定
二,Runtime消息传递
一个对象的方法 [obj test],编译器转成消息发送objc_msgSend(obj, test)
Runtime时执行的流程是这样的:
①.首先,通过obj的isa指针找到它的class;
②.在class的method list找test;
③.如果class中没到test,继续往它的superclass中找 ;
④.一旦找到test这个函数,就去执行它的实现IMP
解释:根据isa找到本类,在本类列表里找,没有就在父类中找,一旦找到,就去执行它的实现IMP(期间有个缓存过程,下次也会先从缓存中找,没有再去缓存,使得找到更加迅速),找不到就进入消息转发相关流程(其实这样的话,封装就有意义了,多次调用同一方法肯定比多个不同方法要快)
这里解释一下isa,imp指针:
isa:
是类指针,之所以说isa是指针是因为Class其实是一个指向objc_class结构体的指针,而isa 是它唯一的私有成员变量,即所有对象都有isa指针(isa位置在成员变量第一个位置)
说一下对 isa 指针的理解, 对象的isa 指针指向哪里?isa 指针有哪两种类型?
isa 等价于 is kind of
实例对象(instance) isa 指向类对象
类对象指 isa 向元类对象
元类对象的 isa 指向元类的基类
isa 有两种类型:纯指针,指向内存地址
Root class(class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的Superclass指向nil;
每个Class都有一个isa指针指向唯一的meta class;
Root class(meta)的Superclass指向Root class(class),也就是NSObject,形成一个回路;
每个meta class的isa指针都指向Root class(meta)。(结合图)
IMP :(Implementation缩写)
(1)它是指向一个方法具体实现的指针,每一个方法都有一个对应的IMP。(可以直接调用方法的IMP指针,来避免方法调用死循环的问题)
(2)当你发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由IMP这个函数指针指向了这个方法实现的。
sel:
方法名称的描述,只记录方法的编号不记录具体的方法,具体的方法是 IMP
3,转发
如果上述都没找到,则进入消息转发
〇,动态方法解析
resolveInstanceMethod(实例方法),resolveClassMethod(类方法)
解释:在此处可以添加一个方法,让你有机会添加,如果不抓住,就进入快速消息转发
①,快速消息转发
forvardingTargetForSelector
解释:这里指定对象执行什么方法,如果指定对象的方法没有实现也会崩溃,如果没指定,则返回nil,到2
②,正常消息转发
methodSignatureForSelector,forwardInvocation
解释:返回方法签名,下一步,返回nil未处理到forwardInvocation( 通过anInvocation处理)
判断当前执行的方法签名是否存在,存在则交给父类处理,不存在,则手动注册一个方法签名,当返回注册的新方法签名后,马上就会开始执行forwardInvocation,forwardInvocation里面可以没操作,但必须实现forwardInvocation方法
③,崩溃:doesnotrecognaziedselector
4,应用
①,获取类的所有属性和方法(获取后改变其属性方法,比如最左侧边滑动返回改为全屏返回)
②,为类添加关联属性(extension中,比如uiviewcontroller添加一个加载动画,uiview添加手势点击及事件回调)
③,为类动态添加方法,消息转发里面该写
④,方法交换(改变加载顺序,将load和init改变顺序,使得显示更加迅速)
⑤,给按钮添加间隔点击,方法交换
5,swift使用
Swift代码中已经没有了Objective-C的运行时消息机制, 在代码编译时即确定了其实际调用的方法. 所以纯粹的Swift类和对象没有办法使用runtime, 更不存在method swizzling.
为了兼容Objective-C, 凡是继承NSObject的类都会保留其动态性, 依然遵循Objective-C的运行时消息机制, 因此可以通过runtime获取其属性和方法, 实现method swizzling.
对于基本框架如Foundation, UIKit等, 都可以使用runtime.
①这里, 要注意Swift的代码与Objective-C代码的语法区别:
同时, 对于一般OC代码的method swizzling, 在load方法中执行即可. 而Swift没有load, 所以要在initialize中执行.
②Swift中的@objc和dynamic关键字:
继承自NSObject的类都遵循runtime, 那么纯粹的Swift类呢?
在属性和方法之前加上@objc关键字, 则一般情况下可以在runtime中使用了. 但有一些情况下, Swift会做静态优化而无法使用runtime.
要想完全使得属性和方法被动态调用, 必须使用dynamic关键字. 而dynamic关键字会隐式地加上@objc来修饰.
获取Swift类的runtime信息的方法, 还要加上Swift模块名。
附加面试题:runtime 如何实现 weak 属性?
首先要搞清楚weak属性的特点:
weak策略表明该属性定义了一种“非拥有关系” (nonowning relationship)。为这种属性设置新值时,
设置方法既不保留新值,也不释放旧值。此特质同assign类似;然而在属性所指的对象遭到摧毁时,属性值也会清空(nil out)
那么runtime如何实现weak变量的自动置nil?
runtime对注册的类,会进行布局,会将 weak 对象放入一个 hash 表中。用 weak 指向的对象内存地址作为 key,
当此对象的引用计数为0的时候会调用对象的 dealloc 方法,假设 weak 指向的对象内存地址是a,那么就会以a为key,
在这个 weak hash表中搜索,找到所有以a为key的 weak 对象,从而设置为 nil。
weak属性需要在dealloc中置nil么?
在ARC环境无论是强指针还是弱指针都无需在 dealloc 设置为 nil , ARC 会自动帮我们处理。
即便是编译器不帮我们做这些,weak也不需要在dealloc中置nil。
在属性所指的对象遭到摧毁时,属性值也会清空