runtime和runLoop

本文详细解析了RunLoop的概念,包括其在事件接收与分发中的角色,如何维持程序运行,处理各类事件,以及节省CPU资源。深入探讨了RunLoop的构成元素,如CFRunLoopRef、CFRunLoopModeRef等,以及RunLoop与线程的关系,应用场景,如AutoreleasePool、定时器、事件响应等。

runtime

即运行时,是一套比较底层的纯C语言API。属于一个C语言库,包含了许多C语言的API。它是OC的幕后工作者。我们平时写的OC代码在运行过程中都会转为runtime的C语言代码。

作用

  1. 获取一个类的全部成员变量,就算是私有成员变量也可以获取的到。
  2. 获取一个类的全部属性名;
  3. 获取一个类的全部方法;
  4. 获取一个类中遵循的全部协议(Protocol);
  5. 归档/解档(IOS数据持久化的一种方式,用来储存对象);
    NSKeyedArchived。比如:person有很多成员变量,用runtime在encodeWithCoder:和initWithCoder方法中取成员变量即可。
  6. 交换两个方法的实现,拦截系统自带的方法调用功能;
    // 获取某个类的类方法
    Method class_getClassMethod(Class class,SEL name)
    // 获取某个类的实例对象方法
    Method class_getInstanceMethod(Class class,SEL name)
    // 交换两个方法的实现
    void method_exchange Implementation(Method m1,Method m2)
    
  7. 在分类中设置属性,给任意一个对象设置属性;

RunLoop

RunLoop是事件接收和分发机制的一个实现,提供了一种异步执行代码的机制,不能并行执行任务,在主队列中,Main RunLoop直接配合任务的执行,负责处理UI事件、定时器以及其他内核相关事件。

RunLoop(消息循环),说白了就是一种事件监听循环,就好比一个while循环,监听到事件就起来,没有就休息。

目的

  1. 当需要和线程进行交互的时候才会使用RunLoop
  2. 需要使用Port或者自定义InputSource与其他线程进行通讯
  3. 子线程中使用了定时器
  4. Cocoa中使用任何performSelector到了线程中运行方法
  5. 线程执行周期性任务,仅当在为你的程序创建辅助线程的时候,你才需要显示运行一个RunLoop

基本作用

  1. 保持程序的持续运行(这也是IOS程序为什么能一直不死的原因)
  2. 处理APP中的各种事件(比如触摸事件、selector事件、定时器事件等)
  3. 节省CPU资源,提供程序性能,有事件就起来,没有就休息

RunLoop对象

  1. 在IOS开发中有两套API来访问RunLoop:Foundation框架【NSRunLoop】和Core Foundation框架【CFRunLoopRef】
  2. NSRunLoop和CFRunLoopRef都代表这RunLoop对象,它们都是等价的,可以互相转换。
  3. NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构需要多研究CFRunLoopRef层面的API

RunLoop与线程

  1. RunLoop和线程的关系:一个RunLoop对应着一条唯一的线程
  2. RunLoop的创建:主线程RunLoop已经创建好了,子线程的RunLoop需要手动创建
  3. RunLoop的生命周期:在第一次获取时创建,在线程结束时销毁
  4. 我们不可以去创建当前线程的RunLoop对象,但是我们可以去获取当前线程的RunLoop

RunLoop构成元素:CFRunLoopRef、CFRunLoopModeRef、CFRunLoopSourceRef、CFRunLoopTimerRef、CFRunLoopObserveRef

  1. 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];
  1. CFRunLoopSourceRef:事件产生的地方。Source有两个版本Source0和Source1
    Source0 包含了一个回调(函数指针),它并不能主动触发事件,使用时,需要先调用CFRunLoopSourceSignal(source)将这个Source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件。
    Source1 包含了一个mach_port和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种Source能主动唤醒RunLoop的线程
  2. CFRunLoopTimerRef:基于时间的触发器。它和NSTimer是toll-free bridge的,可以混用,其包含一个时间长度和一个回调(函数指针),当其加入到RunLoop时,RunLoop会注册相应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。
  3. 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应用场景

  1. AutoreleasePool。APP启动后,在主线程RunLoop里注册了两个Observer,其回调都是一个_wrapRunLoopWithAutoreleasePoolHandle()。第一个Observer监视的事件是Entry(即将进入loop),其回调会调用_objc_autoreleasePoolPush()创建自动释放池。第二个Observer监视了两个事件:BeforeWaiting(准备进入休眠)时调用_objc_autoreleasePoolPush()释放旧的池并创建新池,Exit(即将退出Loop)时调用_objc_autoreleasePoolPop()保证其释放池子发生在其他所有回调之后。
    在主线程执行的代码,通常是写在诸如事件回调。Timer回调内的这些回调会被RunLoop创建好的AutoreleasePool环绕着,所以不会出现内存泄露。开发者也不必显示创建Pool了。
  2. 定时器。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。
  3. PerformSelector。当调用NSObject的performSelector:afterDelay:后,实际上其内部会创建一个Timer并添加到当前线程的RunLoop中,如果当前线程无RunLoop,则这个方法会失效。
    当调用performSelector:onThread:时,实际上会创建一个Timer到对应的线程去,同样,若无RunLoop,该方法失效。
  4. 事件响应
  5. 手势识别
  6. 界面更新
  7. GCD
  8. 网络请求。为了线程保活,AFNetWorking内部也用了runLoop:通过给子线程添加一个runLoop来保证这个子线程不退出。当需要这个子线程执行任务时,AFNetWorking通过调用NSObject的performSelector:onThread:将任务抛给这个子线程的RunLoop。
  9. SDWebImage。SDWebImage中的动画播放类SDAnimatedImageView中也有RunLoop的影子。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值