Runtime与Runloop 精简理解
最近面试总被问道这两方面的问题,以前了解的还是比较片面,把自己理解的做一下总结;
我所记录的不是大神那种把源码一条条给你分析,我说的会通俗易懂针对于面试;
1.什么是Runtime?
一般会想到 【运行时】【获取属性】【获取方法列表】用途【字典转模型】;
OC的函数本质是消息的发送,属于动态调用过程。我们写 OC 代码,它在运行的时候也是转换成了 runtime 方式运行的,会根据函数的名称找到对应的函数来调用。
例子:看下运行时都做了什么
objc = [objc init];//这个方法的调用 下边在runtime 底层实际写法
objc = objc_msgSend(objc, @selector(init));//object是一个objc_object结构体
_msgSend下面在解释
1.1什么是:objc_object 答:动态类型id其实就是一个objc_object。
// 在objc.h中找到相关代码
typedefstructobjc_class*Class;
structobjc_object{
Class isa OBJC_ISA_AVAILABILITY;
};
可以看出一个类的实例,即一个对象,其实在runtime时刻是一个objc_class结构体,而结构体里面只有一个指向objc_class的指针isa。
1.2什么是isa
isa指针. 对象的指针—>类,类的指针—>元类,元类—>根元类—>自己 形成闭环;
1.3什么是objc_class
看到这里是不是知道运行时为什么可获取属性 和方法了 因为这些都在class里
objc_method_list方法链表 先看下objc_method的结构
SEL
从SEL类型的成员为method_name可以知道,SEL大概代表一个方法的名字,作用是标记方法区分方法
IMP
IMP是一个函数指针,指向objc_method对应方法的实现部分。
1.4消息传递与转发机制objc_msgSend()
上面介绍了方法在哪里 接下来是我们在调用方法(发消息)runtime 都做了什么
消息传递
1.objc_msgSend()函数会根据调用的对象isa指针 找到所属的class里面的objc_method_list方法列表
然后从上向下遍历,找到SEL相符的方法名,根据IMP指针跳转到方法的实现代码,调用这个方法的实现
2.如果找不到接收者,会根据所属类的superClass指针,沿着类的继承体系继续向上查找(向父类查找),如果 能找到与名称相符的方法,就根据IMP指针跳转到方法的实现代码,调用这个方法的实现。
3.如果在继承体系中还是找不到相符的方法,此时就会执行”消息转发(message forwarding)“操作。
消息转发
1.如果在整个类的继承体系中还是找不到与相符的方法,也就是对象或者类对象收到了无法解读的消息,那么就会进入到消息转发环节。
2.消息转发分为两个阶段。第一阶段叫做“动态方法解析(dynamic method resolution)”,或者叫“动态方法决议”。第二阶段涉及到“完整的消息转发机制(full forwarding mechanism)”,或者叫“完整的消息转发原理”。
动态方法解析的意思就是,征询消息接受者所属的类,看其是否能动态添加方法,以处理当前“这个未知的选择子(unknown selector)“。实例对象在接受到无法解读的消息后,首先会调用其所属类的下列类方法:
1+ (BOOL)resolveInstanceMethod:(SEL)selector
类对象在接受到无法解读的消息后,那么运行期系统就会调用另外的一个方法,如下:
1+ (BOOL)resolve【瑞走】ClassMethod:(SEL)selector
如果运行期系统已经执行完了动态方法解析,那么消息接受者自己就无法再以动态新增方法的形式来响应包含该未知选择子的消息了,此时就进入了第二阶段——完整的消息转发。运行期系统会请求消息接受者以其他手段来处理与消息相关的方法调用。
(2.2)完整的消息转发
完整的消息转发又分为两个阶段,第一阶段称为备援接受者(replacement receiver),第二阶段才是启动完整的消息转发机制。
(2.2.1)备援接收者(replacement receiver)
当前接受者如果不能处理这条消息,运行期系统会请求当前接受者让其他接受者处理这条消息,与之对应的方法是:
1- (id)forwardingTargetForSelector:(SEL)selector
方法参数代表未知的选择子,返回值为备援接受者,若当前接受者能找到备援接受者,就直接返回,这个未知的选择子将会交由备援接受者处理。如果找不到备援接受者,就返回nil,此时就会启用”完整的消息转发机制“。
(2.2.2)完整的消息转发
如果转发算法已经来到了这一步,那么代表之前的所有转发尝试都失败了,此时只能启用完整的消息转发机制。完整的消息转发机制是这样的:首先创建NSInvocation对象,把尚未处理的那条消息有关的全部细节封装于这个NSInvocation对象中。此对象中包含选择子(selector)、目标(target)及参数。在触发NSInvocation对象时,”消息派发系统(message-dispatch system)“将亲自触发,把消息派发给目标对象。此步骤中会调用下面这个方法来转发消息:
1- (void)forwardInvocation:(NSInvocation *)invocation
消息派发系统触发消息前,会以某种方式改变消息内容,包括 但不限于额外追加一个参数、改变选择子等。
实现此方法时,如果发现调用操作不应该由本类处理,则需要沿着继承体系,调用父类的同名方法,这样一来,继承体系中的每个类都有机会处理这个调用请求,直至rootClass,也就是NSObject类。如果最后调用了NSObject的类方法,那么该方法还会继而调用”doesNotRecognizeSelector:“以抛出异常,此异常表明选择子最终也未能得到处理。消息转发到此结束。
小结
* 若对象无法响应某个选择子,则进入消息转发流程。
* 通过运行期的动态方法解析功能,我们可以在需要用到某个方法时再将其加入类中。
* 对象可以把其无法解读的某些选择子转交给其他对象处理。
* 经过上述两步之后,如果还是没办法处理选择子,那就启动完整的消息转发机制。
1.什么是RunLoop?
通常所说的RunLoop指的是NSRunloop或者CFRunloopRef,CFRunloopRef是纯C的函数,而NSRunloop仅仅是CFRunloopRef的OC封装,内部其实是一个_do while_循环,这也正是Runloop运行的本质,只是不同于我们自己写的循环它在休眠时几乎不会占用系统资源,当然这是由于系统内核负责实现的,也是Runloop精华所在!
Runloop和线程的关系
主线程默认开启runloop 子线程要去获取 两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()
苹果用 RunLoop 实现的功能
AutoreleasePool 自动释放池
RunLoop启动的时候创建autoreleasePool
RunLoop结束的时候销毁autoreleasePool
当RunLoop进行休眠的时候,将会将之前的autoreasePool销毁,同时创建新的autoreleasePool
App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
事件响应
苹果注册了一个 Source1 (基于 mach port 的) 用来接收系统事件,其回调函数为 __IOHIDEventSystemClientQueueCallback()。
当一个硬件事件(触摸/锁屏/摇晃等)发生后,首先由 IOKit.framework 生成一个 IOHIDEvent 事件并由 SpringBoard 接收。_UIApplicationHandleEventQueue() 会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。通常事件比如 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。
手势识别 界面更新
定时器
为什么把NSTimer对象以NSDefaultRunLoopMode(kCFRunLoopDefaultMode)添加到主运行循环以后,滑动scrollview的时候NSTimer却不动了?
nstime对象是在NSDefaultRunLoopMode下面调用消息的,但是当我们滑动scrollview的时候,NSDefaultRunLoopMode模式就自动切换到UITrackingRunLoopMode模式下面,却不可以继续响应nstime发送的消息。所以如果想在滑动scrollview的情况下面还调用nstime的消息,我们可以把nsrunloop的模式更改为NSRunLoopCommonModes