阅读 86

RunLoop

概念

RunLoop是通过内部维护事件循环来对事件/消息进行管理的一个对象

事件循环

  1. 没有事做就休眠,避免占用资源 (用户态 -> 内核态)
  2. 有消息需要处理,立刻被唤醒 (内核态 -> 用户态)
  3. RunLoop事件循环的关键点就是状态的切换,有事就做,无事就休眠

数据结构

CFRunLoop

  1. NSRunLoop是CFRunLoop的封装,提供了面向对象的API

  2. CFRunLoop的几种Mode
    2.1 Pthread
    2.2 currentMode -> CFRunLoopMode
    2.3 Modes 是一个NSMutableSet<CFRunLoopMode *>集合体
    2.4 commonModes

    • 在系统公开的Mode中,有NSDefaultRunLoopMode和UITrackingRunLoopMode,这两个Mode都被标记为commonMode(这两个Mode都是在主线程之上的),UITrackingMode
    • 此外系统还提供了一个操作common标记字符串NSRunLoopCommonModes,可以通过将ModeName添加到RunLoop的CommonModes中(这并不是一个实际意义上的Mode,只是一个Mode的集合体)
    • DefaultMode 是 App 平时所处的状态,TrackingRunLoopMode 是追踪 ScrollView 滑动时的状态

    2.5 CommonModeItems(集合体)

    • 多个obsever
    • 多个Timer
    • 多个Source

CFRunLoopMode(事件管家)

  • name NSDefaultRunLoopMode,通过这个名称来找到RunLoop模式
  • sources0
  • sources1
  • observers
  • timers

Source/Timer/Observer

  • CFRunLoop具备两个事件:source0(需要手动唤醒线程,从内核态切换到用户态),source1(具备自主唤醒线程的能力)
  • CFRunLoopTimer:基于事件的定时器,和timer是toll_free bridge的
  • CFRunLoopObserver:观测时间点
    1. kCFRunLoopEntry(入口时机)
    2. kCFRunLoopBeforeTimers(将要对timer事件进行观测)
    3. kCFRunLoopBeforeSources(将要对sources事件进行观测)
    4. kCFRunLoopBeforeWaiting(将要进入休眠状态,此处发生用户态到内核态的切换)
    5. kCFRunLoopAfterWaiting(唤醒,内核态切换到用户态)
    6. kCFRunLoopExit(RunLoop退出通知)

RunLoop数据结构之间的关系

  • RunLoop和线程为一一对应关系
  • RunLoop对Mode为一一对应关系
  • Mode对Source/Timer/Observer为一对多关系

RunLoop的Mode

  • RunLoop只能同时处理一个Mode下的事件,例如,在滑动时,timer定时器将会失效

事件循环机制

  1. 调用CFRunLoopRun()函数
  2. RunLoop启动之后,发送一个通知,告诉系统即将进入RunLoop
  3. 将要处理Timer/Source0事件
  4. 处理source0事件
  5. 假如有source1事件需要处理,执行一个goto语句,唤醒切换内核态到用户态,进行事件处理,跳转到步骤7
  6. 没有source1事假需要处理,线程即将进入休眠状态,并发送通知
  7. 处理唤醒时收到的信息,之后跳回步骤2
  8. 线程进入休眠状态,发送通知
  9. 即将退出RunLoop,发送通知
  • RunLoop的核心
  1. 用户态 -> main函数 -> mach_msg() -> 系统调用 -> 内核态 -> mach_msg() -> 用户态
  2. 其核心为,用户态和内核态的切换,来完成休眠和唤醒事件

RunLoop和NSTimer事件

  1. 当我们在滑动界面时,会发现timer处于停顿状态,这是因为,timer事件位于defaultMode上,而滑动事件处于UITrackingMode上,而RunLoop同时只能处理一个事件,所以,在滑动tableview时,我们的timer事件就会失效停止
  2. 此时,我们将timer事件添加到CommonMode上,就能解决tableview滑动时,timer失效的问题。因为,UITrackingMode和defaultMode,都是注册在CommonMode中的事件,而timer添加到CommonMode上时,RunLoop,在滑动的时候,相当于只处理了CommonMode,就不会发生timer失效停顿问题

RunLoop和多线程

  1. RunLoop和线程之间是一一对应的
  2. 自己创建的线程,并没有RunLoop事件,需要自己手动添加RunLoop
  3. 实现一个常驻线程
    3.1 为当前线程开启一个RunLoop
    3.2 向该RunLoop中添加一个Port/Sourece等维持RunLoop的事件循环
    3.3 启动该RunLoop(CFRunLoopRun()函数来启动)
  4. 关于performSelector方法
    4.1 在此方法中,把当前线程的对象传递给其他线程对象,此方法会被加入到目标线程的RunLoop中,该runloop使用默认mode--NSRunLoopCommonModes。当该线程的runloop执行的时候,它会以此出队列,然后执行想要执行的方法
[self performSelector:@selector(testRun) onThread:self.thread withObject:nil waitUntilDone:NO];

4.2 所以在它所添加的线程Thread中,我们需要手动开启RunLoop线程,否则testRun方法将不执行

[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];

我们都知道,Runloop启动前内部必须要有一个Timer/Observer/Source,所以在这里的runloop执行run之前,先创建了一个NSPort添加进去了。通常情况下,调用者需要持有这个port,并在外部线程通过这个port发送消息到loop内,但这里添加port只是为了让runloop不至于退出,并没有实际的发送消息。

如何保证子线程数据更细UI时,不打断滑动效果

  1. 滑动的Mode在在UITrackingMode上的(在主线程上)
  2. 数据请求是在子线程进行,切换回主线程进行更新的,我们可以把这块的逻辑包装起来,放置于defaultMode下,在进行滑动的时候,defaultMode停止不会被处理,停止滑动时,不再处理UITrackingMode,这时再进行defaultMode的数据更新
  3. 这边也可以添加一个timer事件到RunLoop中,保活常驻线程,在timer的block回调中执行耗时操作,(操作原理同步骤2)这样也可使tableview滑动流畅(一个常驻线程使用的场景)

作者:叔简

原文链接:https://www.jianshu.com/p/5480cf16563e

文章分类
后端
文章标签
版权声明:本站是系统测试站点,无实际运营。本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 XXXXXXo@163.com 举报,一经查实,本站将立刻删除。
相关推荐