iOS:NSRunLoop浅析

监听RunLoop的状态
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    switch (activity) {
        case kCFRunLoopEntry: {
            CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
            NSLog(@"kCFRunLoopEntry - %@", mode);
            CFRelease(mode);
            break;
        }
            
        case kCFRunLoopExit: {
            CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
            NSLog(@"kCFRunLoopExit - %@", mode);
            CFRelease(mode);
            break;
        }
            
        default:
            break;
    }
});
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);

RunLoop的一些说明:
  • 一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个Source0/Source1/Timer/Observer

  • RunLoop 启动时只能选择其中一个 Mode,作为currentMode;

  • 如果需要切换 Mode,只能退出当前 RunLoop,再重新选择一个 Mode 进入;

    • 不同组的 Source0/Source1/Timer/Observer能分隔开来,互不影响;
  • 如果 Mode 里没有任何 Source0/Source1/Timer/ObserverRunLoop 会立马退出。


RunLoop与线程的关系

线程和 RunLoop 之间是一一对应的,其关系是保存在一个全局的字典里。主线程的 RunLoop 默认是开启的,子线程创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。

RunLoop与自动释放池的关系

系统在主线程的 RunLoop 里注册了两个 Observer

  • 1、第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其优先级最高,保证创建释放池发生在其他所有回调之前;
  • 2、第二个 Observer 监视了两个事件: BeforeWaiting(准备进入睡眠)Exit(即将退出Loop),监听到BeforeWaiting 时调用 _objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 释放旧的池并创建新池;监听到 Exit 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的优先级最低,保证其释放池子发生在其他所有回调之后。
RunLoop与GCD的关系

简单的来说,GCD用到了RunLoop,同时RunLoop用到了GCD:

  • 1、 RunLoop 的超时时间就是使用 GCD 中的 dispatch_source_t 来实现的;
  • 2、当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop 会被唤醒,并从消息中取得这个 block,并在回调 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。

RunLoop的使用场景
  • 定时器(Timer)

    • Timer依赖于RunLoop:只有将Timer添加到 RunLoop 里才有效。
  • PerformSelector

    • 当调用 NSObjectperformSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
    • 当调用 performSelector:onThread: 时,实际上其会创建一个 Timer 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
  • 事件响应

    • 系统注册了一个 Source1 用来接收系统事件。
  • 手势识别

    • 屏幕表面的事件会先包装成 EventEvent先告诉 source1source1 唤醒 RunLoop,然后将事件 Event 分发给 source0,然后由 source0 来处理。
  • 界面刷新

    • 即准备进入睡眠和即将退出 RunLoop 两个时间点,会调用函数更新 UI 界面。
      当在操作 UI 时,某个需要变化的 UIView/CALayer 就被标记为待处理,然后被提交到一个全局的容器去,再在上面的回调执行时才会被取出来进行绘制和调整。
  • AutoreleasePool

    • 见上面 RunLoop与自动释放池的关系
  • 网络请求

  • GCD Async Main Queue

    • 见上面 RunLoop与GCD的关系
  • 监控系统卡顿

    • 监控主线程状态,在一定时间内没有变化,就可判定为卡顿。

RunLoop的运行逻辑

参考

RunLoop与线程与GCD的关系
runloop、自动释放池、线程、GCD

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。
禁止转载,如需转载请通过简信或评论联系作者。

相关阅读更多精彩内容

友情链接更多精彩内容