思考 什么是RunLoop?
RunLoop是通过内部维护的事件循环,来对消息/事件进行管理的一个对象。
思考 事件循环?
1.没有消息、事件需要处理的时候,休眠以避免资源占用
2.有消息需要处理时,立刻被唤醒
是 用户态 到 内核态 的切换
1. RunLoop
RunLoop的结构是:
struct __CFRunLoop {
pthread_t _pthread; # 记录线程
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode; # 记录当前的CFRunLoopMode
CFMutableSetRef _modes; # 保存很多CFRunLoopMode
...省略
}
其中的CFRunLoopMode
的结构如下:
struct __CFRunLoopMode {
CFStringRef _name; # mode名字
CFMutableSetRef _source0; # 事件集合
CFMutableSetRef _source1; # 事件集合
CFMutableArrayRef _observers; # 观察者集合
CFMutableArrayRef _timers; # 定时器集合
}
- CFRunLoopModeRef代表RunLoop的运行模式
- 一个RunLoop包含多个mode,每个mode中又包含多个source0、source1、timer和observer
- RunLoop启动时只能选择其中一个mode作为currentMode
- 如果需要切换mode,只能退出当前循环,再重新选择一个mode进入
- source0指触摸事件和
performSelector: onThread:
方法 - source1指基于Port的线程间通信和系统事件捕捉
- timers指
NSTimer
和performSelector: withObject: afterDelay:
- observer用于监听RunLoop的状态,比如监听到RunLoop即将休眠(BeforeWaiting),就会刷新UI;自动释放池AutoreleasePool也是监听RunLoop来释放
observer可以观察的时间点有:
-
kCFRunLoopEntry
: RunLoop入口时机,当RunLoop准备启动的时候,系统会回调这个系统通知 -
kCFRunLoopBeforeTimers
: 通知观察者RunLoop将要对timer一些事件进行处理 -
kCFRunLoopBeforeSources
: 通知观察者RunLoop将要处理一些source事件 -
kCFRunLoopBeforeWaiting
: 通知观察者当前RunLoop将要进入休眠状态,即将发生用户态到内核态的切换 -
kCFRunLoopAfterWaiting
: 通知观察者当前RunLoop已经唤醒,刚发生内核态到用户态的切换 -
kCFRunLoopExit
: 通知观察者当前RunLoop退出
RunLoop启动后,会首先发送一个通知告诉观察者即将进入RunLoop;
之后RunLoop会向观察者发送一个即将处理Timer和source0事件的通知;
然后RunLoop会正式处理source0事件;
然后如果有source1事件需要处理,则会通过goto跳转代码逻辑到处理source1事件;
如果没有source1事件处理,则线程此时将要休眠,并发送通知,然后进行用户态到内核态的切换,然后线程休眠等待唤醒;
CFRunLoopSource分为两种:
- source0 需要手动唤醒线程
- source1 具备唤醒线程的能力
唤醒线程的条件有:
- source1
- timer事件的回调
- 外部手动唤醒
一个RunLoop有多个mode,如果当前RunLoop运行在mode1上,那么mode2上的事件回调等会被屏蔽掉,无法接受。比如滑动tableview列表时候,cell上的timer不调用就是因为这个问题。
如果一个timer想要在多个mode下都可以运行,可以把timer加到NSRunLoopCommonModes上。
注意:
- CommonMode不是实际存在的一种mode
- 它是同步Source、timer、observer到多个mode中的一种技术方案
2. RunLoop与线程
RunLoop与线程的联系:
- RunLoop与线程是一一对应的关系
- 线程创建的时候并没有RunLoop对象
- RunLoop会在第一次获取它的时候创建,调用
[NSRunLoop currentRunLoop]
获取RunLoop - RunLoop保存在一个全局的Dictionary中,
key
是线程,value
是RunLoop - 主线程在启动的时候,会自动获取RunLoop对象
- 子线程默认没有开启RunLoop
- RunLoop会在线程结束时候销毁
3. RunLoop在实际开发中的应用
- 控制线程声明周期(线程保活AF)
- 解决NSTimer在列表滑动的时候停止工作的问题
- 监控应用卡顿
- 性能优化
思考 怎样实现一个常驻线程?
1.为当前线程开启一个RunLoop;
currentRunLoop方法会查找当前线程是否有RunLoop,如果没有则系统会为我们创建一个
2.向该RunLoop中添加一个port/source等维持RunLoop的事件循环
3.启动该RunLoop
static BOOL runAlways = YES;
static NSThread *thread = nil;
+ (NSThread *)threadForDispathc {
if (nil == thread) {
@synchronized (self) {
if (nil == thread) {
thread = [[NSThread alloc] initWithTarget: self
selector: @selector(runRequest)
object: nil];
[thread setName: @"com.zmj"];
[thread start];
}
}
}
return thread;
}
+ (void)runRequest {
// 创建一个source
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef sourceRef = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 创建RunLoop,同时向RunLoop的DefaultMode下面添加Source
CFRunLoopAddSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopDefaultMode);
while (runAlways) {
@autoreleasepool {
// 令当前RunLoop运行在DefaultMode下
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
}
}
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), sourceRef, kCFRunLoopDefaultMode);
CFRelease(sourceRef);
}
# 注意:运行的模式和添加的mode需要是同一个,比如上图的kCFRunLoopDefaultMode
- (void)exitRunLoop {
runAlways = NO;
}
思考:怎样保证子线程数据回来更新UI的时候,不打断用户的滑动操作?
当用户滑动的时候,当前的RunLoop运行在trackingMode模式下,我们可以把子线程抛会主线程更新UI这段逻辑封装到主线程的defaultMode下
这样抛回来的任务当用户滑动的时候,就不会执行打断用户滑动;当滑动结束后,主线程切回到defaultMode就可以执行更新UI数据。
思考:如何在列表滑动的时候,让定时器继续工作?
第一种:把timer定时器添加到CommonMode
第二种:另起一个子线程,在子线程中开启RunLoop
# 第一种
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: 1 repeats: YES block:^(NSTimer * _Nonnull timer) {
}];
[[NSRunLoop currentRunLoop] addTimer: timer forMode: NSRunLoopCommonModes];
# 第一种
dispatch_queue_t concurrent_t = dispatch_queue_create("com.zmj", DISPATCH_QUEUE_CONCURRENT);
dispatch_async(concurrent_t, ^{
[NSTimer scheduledTimerWithTimeInterval: 1 repeats: YES block:^(NSTimer * _Nonnull timer) {
}];
[[NSRunLoop currentRunLoop] run];
});
知识拓展: https://github.com/bestswifter/blog/blob/master/articles/ios-runloop.md
https://juejin.cn/post/6844903878299746318