系统层知识RunLoop

NSRunLoop总结 和 安卓Looper 不同

2018-11-29  本文已影响0人  介和

  目录

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 CFRunLoopObserverRefAutoreleasePool

主线程 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 安卓的LooperiOSNSRunLoop不同点

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 源码学习

上一篇下一篇

猜你喜欢

热点阅读