iOS|iOS RunLoop

RunLoop产生背景
由于内存频繁创建线程,会使内存的负担加重,所以想要实现长期持有个别会持续工作的线程,让它有工作的时候工作,没有工作的时候休息,因此就产生了RunLoop。RunLoop就是通过内部事件循环来管理内存中事件及消息的一个对象,实现是通过do-while循环。
RunLoop和线程的关系
Runloop和线程是一一对应的关系,主线程的RunLoop默认是开启的,其它子线程的Runloop需要调用相应的方法手动开启。RunLoop在内存中是存在字典里,线程作为key,RunLoop作为value。
RunLoop分类

  • NSRunLoop
  • CFRunLoopRef
RunLoop的工作
RunLoop在循环中处理各种出现的事件,比如触摸事件、UI刷新事件、定时器事件、Selector事件等。

iOS|iOS RunLoop
文章图片
runloop跑圈.jpg RunLoop工作模式
一个RunLoop可以包含若干个Mode,Mode里包含Source/Timer/Observer(runloop需要处理的事件,如果Mode里不包含任何Source/Timer/Observer的话,runloop就退出了),但是RunLoop一次只能工作在一个Mode模式下,切换Mode需要退出RunLoop重新进入。(这个地方不能确定,但是大家都是这么说的。个人疑惑既然包含多个Mode,为什么还要先退出再进入呢?望看到的同仁可以告知一下为什么是这样)

iOS|iOS RunLoop
文章图片
RunLoop中的Modes.png
RunLoop和Mode结构
struct __CFRunLoopMode { CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode" CFMutableSetRef _sources0; // Set CFMutableSetRef _sources1; // Set CFMutableArrayRef _observers; // Array CFMutableArrayRef _timers; // Array ... }; struct __CFRunLoop { CFMutableSetRef _commonModes; // Set CFMutableSetRef _commonModeItems; // Set CFRunLoopModeRef _currentMode; // Current Runloop Mode CFMutableSetRef _modes; // Set ... };

Mode类型
  1. kCFRunLoopDefaultMode
    App的默认Mode,通常主线程是在这个Mode下运行
  2. UITrackingRunLoopMode:
    界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
  3. UIInitializationRunLoopMode:
    在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
  4. GSEventReceiveRunLoopMode:
    接受系统事件的内部 Mode,通常用不到
  5. kCFRunLoopCommonModes:
    这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
Mode中的Source源
Source有两个版本:Source0 和 Source1。
  1. Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
  2. Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程.
RunLoop工作原理
iOS|iOS RunLoop
文章图片
runloop运行逻辑.png RunLoop与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,优先级最低,保证其释放池子发生在其他所有回调之后。
注意 参考:https://www.jianshu.com/p/e48f41d2144d
  • AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组成
  • AutoreleasePoolPage每个对象会开辟4096字节内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间,剩下的空间全部用来储存autorelease对象的地址
  • iOS里的TaggedPointer不适用autorelesepool
  • NSAutoreleasePool可以创建一个autorelease pool,但该对象本身也需要被释放 drain
  • 在ARC下,不能使用上述方式调用autorelease,而应当使@autoreleasepool{}
  • 对于不同线程,应当创建自己的autorelease pool。如果应用长期存在,应该定期drain和创建新的autorelease pool。
  • runloop 与 AutoreleasePool 是协同合作关系(看到有文章说子线程不需要手动添加AutoreleasePool ,可以确定的小伙伴看到给指点一下是否需要)
  • AutoreleasePool 与 runloop 与线程是一一对应的关系
  • AutoreleasePool 在 runloop 在开始时被push,在runloop休眠时(beforewaiting状态)pop
RunLoop的应用
  • NSTimer和UITableView
  • 常驻线程(线程保活)
参考 【iOS|iOS RunLoop】https://blog.ibireme.com/2015/05/18/runloop/
https://blog.csdn.net/shihuboke/article/details/78417299

    推荐阅读