runtime
即运行时,是一套比较底层的纯C语言API。属于一个C语言库,包含了许多C语言的API。它是OC的幕后工作者。我们平时写的OC代码在运行过程中都会转为runtime的C语言代码。
作用
- 获取一个类的全部成员变量,就算是私有成员变量也可以获取的到。
- 获取一个类的全部属性名;
- 获取一个类的全部方法;
- 获取一个类中遵循的全部协议(Protocol);
- 归档/解档(IOS数据持久化的一种方式,用来储存对象);
NSKeyedArchived。比如:person有很多成员变量,用runtime在encodeWithCoder:和initWithCoder方法中取成员变量即可。 - 交换两个方法的实现,拦截系统自带的方法调用功能;
// 获取某个类的类方法 Method class_getClassMethod(Class class,SEL name) // 获取某个类的实例对象方法 Method class_getInstanceMethod(Class class,SEL name) // 交换两个方法的实现 void method_exchange Implementation(Method m1,Method m2) - 在分类中设置属性,给任意一个对象设置属性;
RunLoop
RunLoop是事件接收和分发机制的一个实现,提供了一种异步执行代码的机制,不能并行执行任务,在主队列中,Main RunLoop直接配合任务的执行,负责处理UI事件、定时器以及其他内核相关事件。
RunLoop(消息循环),说白了就是一种事件监听循环,就好比一个while循环,监听到事件就起来,没有就休息。
目的
- 当需要和线程进行交互的时候才会使用RunLoop
- 需要使用Port或者自定义InputSource与其他线程进行通讯
- 子线程中使用了定时器
- Cocoa中使用任何performSelector到了线程中运行方法
- 线程执行周期性任务,仅当在为你的程序创建辅助线程的时候,你才需要显示运行一个RunLoop
基本作用
- 保持程序的持续运行(这也是IOS程序为什么能一直不死的原因)
- 处理APP中的各种事件(比如触摸事件、selector事件、定时器事件等)
- 节省CPU资源,提供程序性能,有事件就起来,没有就休息
RunLoop对象
- 在IOS开发中有两套API来访问RunLoop:Foundation框架【NSRunLoop】和Core Foundation框架【CFRunLoopRef】
- NSRunLoop和CFRunLoopRef都代表这RunLoop对象,它们都是等价的,可以互相转换。
- NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构需要多研究CFRunLoopRef层面的API
RunLoop与线程
- RunLoop和线程的关系:一个RunLoop对应着一条唯一的线程
- RunLoop的创建:主线程RunLoop已经创建好了,子线程的RunLoop需要手动创建
- RunLoop的生命周期:在第一次获取时创建,在线程结束时销毁
- 我们不可以去创建当前线程的RunLoop对象,但是我们可以去获取当前线程的RunLoop
RunLoop构成元素:CFRunLoopRef、CFRunLoopModeRef、CFRunLoopSourceRef、CFRunLoopTimerRef、CFRunLoopObserveRef
- CFRunLoopModeRef。 一个run loop mode是由若干个sourece、Timer、Observer的集合。它能帮我们过滤掉一些不想要的事件。一个RunLoop在一个mode下运行时,不会接收和处理其他mode的事件,要保持一个mode活着,就必须往里面添加至少一个source、timer或observer。
CFRunLoopMode有四个。kCFRunLoopDefaultMode、kCFRunLoopTrackingMode、UIInitializationRunLoopMode、kCFRunLoopCommonModes。
其中,苹果公开的mode有两个。kCFRunLoopDefaultMode和kCFRunLoopTrackingMode。前者是默认的模式,程序运行的大多时候都处于该mode下,后者是滑动tableView或是scrollView时为了界面流畅而用的mode。
UnInitalizationRunLoopMode是程序启动时进入的mode,一般用不上。
kCFRunLoopCommonModes:伪mode,是若干个mode的集合。比如,让两个互不干扰的mode都做同一件事情。
CommonMode本质上是一个集合,该集合储存的是mode的名字,也就是字符串,记录所有被标记为common的modeName,当我们向commonMode添加source/timer/observer时,本质上是遍历这个集合中的所有mode,把item一次添加到每个标记为common的mode中。
面试题解答
为什么NStimer的倒计时在tableview滑动的时候会卡主不走?
在程序启动时,主线程的RunLoop有两个预置的mode:kCFRunLoopDefaultMode和UITrackingRunLoopMode。默认情况下是会处于defaultMode下,滑动scrollView列表时,RunLoop会退出defaultMode转而进入trackingMode,所以有时候我们加到defaultMode中的timer事件,在滑动时是不会执行的。不过kCFRunLoopDefaultMode和UITrackingRunLoopMode这两个Mode都已经被添加到RunLoop的commonMode集合中了,也就是说,主线程的这两个预置mode默认已经被标记为commonMode,想要我们的timer回调可以在滑动列表的时候依旧执行,只需要把timer这个item添加到commonMode。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
- CFRunLoopSourceRef:事件产生的地方。Source有两个版本Source0和Source1
Source0 包含了一个回调(函数指针),它并不能主动触发事件,使用时,需要先调用CFRunLoopSourceSignal(source)将这个Source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件。
Source1 包含了一个mach_port和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种Source能主动唤醒RunLoop的线程 - CFRunLoopTimerRef:基于时间的触发器。它和NSTimer是toll-free bridge的,可以混用,其包含一个时间长度和一个回调(函数指针),当其加入到RunLoop时,RunLoop会注册相应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
- CFRunLoopObserverRef:观察者。每个Observer都包含了一个回调(函数指针)。当RunLoop的状态发生变化时,观察者就能通过回调接受到这个变化可以观测的时间点。
[1]. kCFRunLoopEntry 即将进入loop
[2]. kCFRunLoopBeforeTimes 即将处理Timer
[3]. kCFRunLoopBeforeWaiting 即将进入休眠
[4]. kCFRunLoopAfterWaiting 刚从休眠中唤醒
[5]. kCFRunLoopExit 即将退出Loop
Source、Timer、Observer被统称为item。一个item可以被同时加入多个mode,但一个item被重复加入同一个mode时是不会有效果的,如果一个mode中一个item都没有,则RunLoop会直接退出,不进入循环。
RunLoop应用场景
- AutoreleasePool。APP启动后,在主线程RunLoop里注册了两个Observer,其回调都是一个_wrapRunLoopWithAutoreleasePoolHandle()。第一个Observer监视的事件是Entry(即将进入loop),其回调会调用_objc_autoreleasePoolPush()创建自动释放池。第二个Observer监视了两个事件:BeforeWaiting(准备进入休眠)时调用_objc_autoreleasePoolPush()释放旧的池并创建新池,Exit(即将退出Loop)时调用_objc_autoreleasePoolPop()保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调。Timer回调内的这些回调会被RunLoop创建好的AutoreleasePool环绕着,所以不会出现内存泄露。开发者也不必显示创建Pool了。 - 定时器。NSTimer其实就是CFRunLoopTimerRef。它们之间是toll-free bridged的。一个NSTimer注册到RunLoop后,RunLoop会为其重复的时间点注册好事件。例如:10:00,10:10,10:20这几个时间点,RunLoop为了节省资源,并不会在非常准确的时间点回调这个Timer。Timer有个属性叫做Tolerance(宽容度),标识了当时间点到后,容许有多少最大误差。如果某个时间点被错过了,例如执行了一个很长的任务,则那个时间点的回调也会跳过去,不会延期执行。
CADisplayLink是一个和屏幕刷新率一致的定时器(但实际实现原理更复杂,和NSTimer并不一样,其内部实际是操作了一个Source)。如果在两次屏幕刷新之间执行了一个长任务,那其中就会有一个帧被跳过去(和NSTimer相似),造成界面卡顿。FaceBook开源的AsyncDisplayLink就是为了解决界面卡顿的问题,其内部也用到了RunLoop。 - PerformSelector。当调用NSObject的performSelector:afterDelay:后,实际上其内部会创建一个Timer并添加到当前线程的RunLoop中,如果当前线程无RunLoop,则这个方法会失效。
当调用performSelector:onThread:时,实际上会创建一个Timer到对应的线程去,同样,若无RunLoop,该方法失效。 - 事件响应
- 手势识别
- 界面更新
- GCD
- 网络请求。为了线程保活,AFNetWorking内部也用了runLoop:通过给子线程添加一个runLoop来保证这个子线程不退出。当需要这个子线程执行任务时,AFNetWorking通过调用NSObject的performSelector:onThread:将任务抛给这个子线程的RunLoop。
- SDWebImage。SDWebImage中的动画播放类SDAnimatedImageView中也有RunLoop的影子。
本文详细解析了RunLoop的概念,包括其在事件接收与分发中的角色,如何维持程序运行,处理各类事件,以及节省CPU资源。深入探讨了RunLoop的构成元素,如CFRunLoopRef、CFRunLoopModeRef等,以及RunLoop与线程的关系,应用场景,如AutoreleasePool、定时器、事件响应等。
782

被折叠的 条评论
为什么被折叠?



