监听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/Observer,RunLoop会立马退出。
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里才有效。
- Timer依赖于RunLoop:只有将Timer添加到
-
PerformSelector
- 当调用
NSObject的performSelecter:afterDelay:后,实际上其内部会创建一个Timer并添加到当前线程的RunLoop中。所以如果当前线程没有RunLoop,则这个方法会失效。 - 当调用
performSelector:onThread:时,实际上其会创建一个Timer加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
- 当调用
-
事件响应
- 系统注册了一个
Source1用来接收系统事件。
- 系统注册了一个
-
手势识别
- 屏幕表面的事件会先包装成
Event,Event先告诉source1,source1唤醒RunLoop,然后将事件Event分发给source0,然后由source0来处理。
- 屏幕表面的事件会先包装成
-
界面刷新
- 即准备进入睡眠和即将退出
RunLoop两个时间点,会调用函数更新 UI 界面。
当在操作 UI 时,某个需要变化的 UIView/CALayer 就被标记为待处理,然后被提交到一个全局的容器去,再在上面的回调执行时才会被取出来进行绘制和调整。
- 即准备进入睡眠和即将退出
-
AutoreleasePool
- 见上面
RunLoop与自动释放池的关系。
- 见上面
网络请求
-
GCD Async Main Queue
- 见上面
RunLoop与GCD的关系。
- 见上面
-
监控系统卡顿
- 监控主线程状态,在一定时间内没有变化,就可判定为卡顿。
