前言
在提到OC的时候,我们不可避免的会提到 Runloop 和 Runtime。但是Runloop到底是怎么工作的,我之前一直没有研究过,今天开看一下。
Runloop简单介绍
RunLoop
就是事件循环,在程序运行过程中循环做一些事情。
基本作用
- 保持程序持续运行
- 处理
App
中的各种事件(触摸事件、定时器事件等) - 节省
CPU
资源,提高程序性能: 该做事时候做事,该休息时休息(普通while
循环是忙等待,RunLoop
是闲等待)
应用范畴
- 定时器(
Timer
)、PerformSelector
GCD Async Main Queue
- 时间响应、手势识别、界面刷新
- 网络请求
AutoreleasePool
RunLoop与线程的关系
- 每条线程都有唯一的一个与之对应的
RunLoop
对象 -
RunLoop
保存在一个全局的Dictionary
里,线程作为key
,RunLoop
作为value
- 线程刚创建时并没有
RunLoop
对象,RunLoop
会在第一次获取它时创建 -
RunLoop
会在线程结束时销毁 - 主线程的
RunLoop
已经自动获取(创建),子线程默认没有开启RunLoop
自动释放池
自动释放池的底层数据结构主要是 __AtAutoreleasePool、AutoreleasePollPage。调用了 autorelease 的对象最终都是通过 AutoreleasePollPage 对象管理的。
AutoReleasePoolPage的结构如下:
AutoReleasePoolPage_struct.png
- 每个 AutoreleasePoolPage 对象占用 4096 字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放 autorelease 对象的地址
- 所有的 AutoreleasePoolPage 对象通过双向链表的形式连接在一起
- 调用push方法会将一个 POOL_BOUNDARY 入栈,并且返回其存放的内存地址
- 调用 pop 方法时传入一个 POOL_BOUNDARY 的内存地址,会从最后一个入栈的对象开始发送 release 消息,直到遇到这个 POOL_BOUNDARY
-
id *next
指向了下一个能存放 autorelease 对象地址的区域
RunLoop 和 AutoreleasePool 的关系
主线程中的RunLoop注册了2个Observer
- 第一个 Observer 监听了 kCFRunLoopEntry 事件,会调用 objc_autoreleasePoolPush()
- 第二个 Observer 监听了2个事件
- 监听了 kCFRunLoopBeforeWaiting 事件,会调用 objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
- 监听了 kCFRunLoopBeforeExit 事件,会调用 objc_autoreleasePoolPop()
获取RunLoop对象:
-
Foundation
-
[NSRunLoop currentRunLoop]
; // 获得当前线程的RunLoop对象 -
[NSRunLoop mainRunLoop]
; // 获得主线程的RunLoop对象
-
-
Core Foundation
-
CFRunLoopGetCurrent()
; // 获得当前线程的RunLoop对象 -
CFRunLoopGetMain()
; // 获得主线程的RunLoop对象
-
CoreFoundation
中关于RunLoop
有5个类:
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
RunLoopMode
RunLoopMode
是RunLoop
的运行模式,RunLoop
总是在各种Mode
之间切换
-
CFRunLoopModeRef
代表RunLoop
的运行模式-
UIInitializationRunLoopMode
: 在刚启动 App 时第进入的第一个Mode
,启动完成后就不再使用。 -
GSEventReceiveRunLoopMode
: 接受系统事件的内部Mode
,通常用不到。 -
kCFRunLoopDefaultMode(NSDefaultRunLoopMode)
:App
默认的Mode
,通常主线程是在这个Mode
下运行 -
UITrackingRunLoopMode
: 界面跟踪Mode
,用于ScrollView
追踪触摸滑动,保证界面滑动时不受其他Mode的影响 -
kCFRunLoopCommonModes
(NSRunLoopCommonModes
): 这是一个占位的Mode
, 无论当前是哪种Mode
,都可以执行这个该Mode
下的事件
-
- 一个
RunLoop
包含若干个Mode
,每个Mode
又包含若干个Source0/Source1/Timer/Observer
-
RunLoop
启动时只能选择其中一个Mode
,作为currentMode
- 如果需要切换
Mode
,只能退出当前Loop
,再重新选择一个Mode
进入 - 不同组的
Source0/Source1/Timer/Observer
能分隔开来,互不影响 - 如果
Mode
里没有任何Source0/Source1/Timer/Observer
,RunLoop
会立马退出
RunLoop 执行流程
先上图,网上找的RunLoop
执行流程:
运行逻辑:
信号源解释:
- Source0
- 触摸事件处理
performSelector:onThread:
- Source1
- 基于
Port
的线程间通信 - 系统事件捕捉,触摸屏幕的事件是先触发
Source1
,然后再由Source1
包装成Source0
- 基于
- Timers
NSTimer
performSelector:withObject:afterDelay:
- Observers
- 用于监听
RunLoop
的状态 - UI刷新(
BeforeWaiting
),当代码设置UI
变动时,并不会立即处理,而是会等到RunLoop
即将进入睡眠状态的时候,统一刷新UI
-
Autorelease pool
(BeforeWaiting
),pool
中的数据并不会立即释放,而是在RunLoop
即将睡眠的时候才会释放pool
中的数据
- 用于监听
RunLoop状态
type | explain |
---|---|
KCFRunLoopEntry | 即将进入Loop |
KCFRunLoopBeforeTimers | 即将处理Timer |
KCFRunLoopBeforeSources | 即将处理Source |
KCFRunLoopBeforeWaiting | 即将进入休眠 |
KCFRunLoopAfterWaiting | 刚从休眠中唤醒 |
KCFRunLoopExit | 即将退出Loop |
KCFRunLoopAllActivities | 所有状态改变 - 监听时用来监听所有状态 |
线程休眠原理:
- 当需要休眠时,线程从用户态切换到内核态(
mach_msg()
) - 当需要处理消息时候,线程从内核态切换到用户态并处理消息(
mach_msg()
)
NSTimer “不准时” 问题
问题: NSTimer依赖于 NSRunLoop,如果 RunLoop的任务过于繁重,可能会导致 NSTimer 不准时
解决办法: 使用GCD定时器
实际开发中的运用
- 控制线程生命周期(线程保活)
- 解决
NSTimer
在滑动时停止工作的问题 - 监控应用卡顿
- 性能优化
最后
以上就是我学习的全部内容,势必会有遗漏或错误的地方,欢迎斧正~
网友评论