NSRunLoop总结 和 安卓Looper 不同
目录
1 什么是NSRunLoop ? 2
1.1 简化一下: 2
1.2 形象一下: 2
2 NSRunLoop与 AutoreleasePool 关系 4
2.1 AutoreleasePool 4
2.2 CFRunLoopObserverRef与AutoreleasePool 5
3 NSRunLoop 与性能 6
3.1 TableView延迟加载图片 6
4 NSRunloop与性能监控 6
5 NSRunLoop 与 线程 7
5.1创建获得runloop 7
5.2使用 NSRunLoop 创建常驻线程 8
5.3 避免使用 GCD Global队列创建NSRunLoop常驻线程 8
6 安卓的Looper与iOS的NSRunLoop不同点 9
6.1 消息的等待和处理方式不同 9
Looper: 9
NSRunLoop: 9
6.2 管理事务不同 10
--------------------------------------------------------------------------------------------
1 什么是NSRunLoop ?
apple官方文档描述:
"run loop 是用来在线程上管理事件异步到达的基础设施; run loop 在没有任何事件处理的时候会把它的线程置于休眠状态,它消除了消耗CPU周期轮询,并防止处理器本身进入休眠状态并节省电源"
1.1 简化一下:
1 用来监听长耗时的异步事件 ;
2消除CPU空转才是它最大的用处。
1.2 形象一下:
runloop就是一个对象,如果把线程比作一条高速公路,我的理解runoop就是这条道路的管理员,没事了就睡觉,有事了把他叫醒。
Source 0:
处理如UIEvent,CFSocket这样的事件 (触摸事件、按钮点击事件)。
使用时,你需要先调用CFRunLoopSourceSignal(source),将这个 Source 标记为待处理 signal 状态,然后手动调CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。
Source 1:
基于Port的,通过内核和其他线程通信,接收分发系统事件 Mach port驱动,CFMachport,CFMessagePort。 (触摸硬件,通过 Source1 接收和分发系统事件到 Source0 处理)
系统默认注册了5个Mode:
NSDefaultRunLoopMode:App 的默认 Mode,通常主线程是在这个 Mode 下运行(默认情况下运行
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响(操作 UI 界面的情况下运行)
UIInitializationRunLoopMode:在刚启动 App 时进入的第一个 Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到(绘图服务)
NSRunLoopCommonModes:这是一个占位用的 Mode,不是一种真正的 Mode (RunLoop无法启动该模式,设置这种模式下,默认和操作 UI 界面时线程都可以运行,但无法改变 RunLoop 同时只能在一种模式下运行的本质)
2 NSRunLoop与 AutoreleasePool 关系
2.1 AutoreleasePool
AutoreleasePoolPage每个对象会开辟4096字节内存一个空的 AutoreleasePoolPage 的内存结构如下图所示:
magic 用来校验 AutoreleasePoolPage 的结构是否完整;
next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin() ;
thread 指向当前线程; AutoreleasePool是按线程一一对应的
parent 指向父结点,第一个结点的 parent 值为 nil ;
child 指向子结点,最后一个结点的 child 值为 nil ;
depth 代表深度,从 0 开始,往后递增 1;
hiwat 代表 high water mark 。
AutoreleasePool并没有单独的结构,而是由若干个AutoreleasePoolPage以双向链表的形式组合而成(分别对应结构中的parent指针和child指针)
2.2 CFRunLoopObserverRef与AutoreleasePool
主线程 RunLoop 里注册了两个 Observer:
第一个 Observer:
监视的事件是Entry(即将进入Loop),其回调内会调用_objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer :
监视了两个事件: BeforeWaiting(准备进入睡眠) 和Exit(即将退出Loop),
BeforeWaiting(准备进入睡眠)时调用
_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush()
释放旧的池并创建新池;
Exit(即将退出Loop) 时调用
_objc_autoreleasePoolPop()
来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
3 NSRunLoop 与性能
3.1 TableView延迟加载图片
当滑动界面的时候,主线程的RunLoopMode会切换到NSEventTrackingRunLoopMode,RunLoop在处理滑动事件,这时候我们还要RunLoop去处理大图片的加载,IO操作是很耗时的操作,所以就造成了卡顿现象。
即在主线程的 RunLoop 中添加一个 Observer,监听了 kCFRunLoopBeforeWaiting 和 kCFRunLoopExit 事件,在收到回调时,遍历所有之前放入队列的待处理的任务,然后一一执行。
例子:1 ASDK中_ASAsyncTransactionGroup
2 https://github.com/bravegogo/OC-demo (https://www.jianshu.com/p/1442467f0496)
4 NSRunloop与性能监控
参考:《iOS UI线程卡顿监控总结》
5 NSRunLoop 与 线程
5.1创建获得runloop
[NSRunLoopcurrentRunLoop]
[NSRunLoop mainRunLoop]
CFRunLoopGetCurrent和 CFRunLoopGetMain - 获取线程的 RunLoop 对象,苹果不允许我们创建 RunLoop,只能通过函数获取当前线程的 RunLoop 对象。
1. 线程和 RunLoop 是一一对应的,对应关系保存在一个以key-value的方式全局的 dictionary 中。
2. RunLoop 创建过程类似懒加载,只有在第一次获取的时候才创建,所以一个线程只有唯一以其对应的 RunLoop 对象。
3. 主线程的 RunLoop 会在初始化全局字典时创建,子线程的RunLoop会在第一次获取的时候创建,如果不获取的话就一直不会被创建。
4. RunLoop 会在线程销毁时而销毁。
5.2使用 NSRunLoop 创建常驻线程
这是 AFNetWorking 2.x 中的常驻线程代码:
[runloop addPort: [NSMachPort port] forMode: NSDefaultRunLoopMode];
这行代码的目的是添加一个端口监听这个端口的事件,这也是我们后面会讲到的一种线程见的通信方式-基于端口的通信。
[runloop run];
runloop 开始跑起来,但是要注意,这种runloop,只有一种方式能停止。
[[NSRunloop currentRunloop] removePort: [NSMachPort port] forMode: NSDefaultRunLoopMode]
只有从runloop中移除我们之前添加的端口。
5.3 避免使用 GCD Global队列创建NSRunLoop常驻线程
dispatch_async函数分发到全局队列不一定会新建线程执行任务,全局队列底层有一个的线程池,如果线程池满了,那么后续的任务会被 block 住,等待前面的任务执行完成,才会继续执行。
如果线程池中的线程长时间不结束,后续堆积的任务会越来越多,此时就会存在 APP crash的风险。
6 安卓的Looper与iOS的NSRunLoop不同点
6.1 消息的等待和处理方式不同
Looper:
利用Linux系统中的管道(pipe)进程间通信机制来实现消息的等待和处理。
1、epoll_wait 设置为阻塞模式。
2、write ( ) 向管道(pipe)写 ,发出一个通知。
3、 read ( ) 从 管道(pipe)读,接收一个通知, Looper 知道有事件需要处理。
NSRunLoop:
NSRunLoop 的核心就是一个 mach_msg(),RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态。
(例如,你在模拟器里跑起一个 iOS 的 App,然后在 App 静止时点击暂停,你会看到主线程调用栈是停留在 mach_msg_trap() 这个地方。)
NSRunLoop的挂起与唤醒
1.制定用于唤醒的mach_port端口
2.调用mach_msg监听唤醒端口,被唤醒前,系统内核将这个线程挂起,停留在mach_msg_trap
3.由另外一个线程(或另一个进程中的某个线程)向内核发送这个端口的msg后,trap状态被唤醒,RunLoop继续开始干活
6.2 管理事务不同
NSRunLoop 和 autoreleasePool 有着巨大的关联,管理着内存的收集与清除 。
见:2.2 小节
参考:
https://www.jianshu.com/p/e259bf7ab297 RunLoop总结:RunLoop 与GCD 、Autorelease Pool之间的关系
https://www.cnblogs.com/itlover2013/p/5650729.html RunLoop运行循环机制
https://github.com/bravegogo/OC-demo
https://www.jianshu.com/p/719cfd7d0a2a RunLoop-UITableViewCell加载高清大图的速度优化
https://blog.ibireme.com/2015/05/18/runloop/ 深入理解RunLoop
https://www.jianshu.com/p/284b1777586c Mach原语:一切以消息为媒介
http://www.cnblogs.com/chenxianming/p/5550669.html 探索 NSRunLoop (二)(NSRunLoop 自己动手实现SimpleRunLoop)
https://www.jianshu.com/p/7a970fc5343b 从安卓的Looper到iOS的RunLoop
https://www.jianshu.com/p/a1cd12215e0a [iOS]runloop 轻量级实现LightWeightRunloop 源码学习