iOS RunLoop

RunLoop产生背景

由于内存频繁创建线程,会使内存的负担加重,所以想要实现长期持有个别会持续工作的线程,让它有工作的时候工作,没有工作的时候休息,因此就产生了RunLoop。RunLoop就是通过内部事件循环来管理内存中事件及消息的一个对象,实现是通过do-while循环。

RunLoop和线程的关系

Runloop和线程是一一对应的关系,主线程的RunLoop默认是开启的,其它子线程的Runloop需要调用相应的方法手动开启。RunLoop在内存中是存在字典里,线程作为key,RunLoop作为value。

RunLoop分类

  • NSRunLoop
  • CFRunLoopRef

RunLoop的工作

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


runloop跑圈.jpg

RunLoop工作模式

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

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工作原理

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
  • 常驻线程(线程保活)
参考

https://blog.ibireme.com/2015/05/18/runloop/
https://blog.csdn.net/shihuboke/article/details/78417299

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

相关阅读更多精彩内容

  • RunLoop 的概念 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线...
    Mirsiter_魏阅读 692评论 0赞 2
  • 转自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飘金阅读 1,103评论 0赞 4
  • RunLoop 是 iOS 和 OS X 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,...
    iOS_Alex阅读 996评论 0赞 10
  • 1 Runloop机制原理 深入理解RunLoop http://www.cocoachina.com/ios/2...
    Kevin_Junbaozi阅读 4,291评论 4赞 30
  • 概述 RunLoop作为iOS中一个基础组件和线程有着千丝万缕的关系,同时也是很多常见技术的幕后功臣。尽管在平时多...
    阳明AI阅读 1,197评论 0赞 17

友情链接更多精彩内容