前言
RunLoop 是 iOS 应用于线程中的一种循环机制。系统本身没有创建 RunLoop 的 API,不过可以通过 currentRunLoop 获取当前 RunLoop。主线程本身就存在一个 RunLoop,而且是运行转态,子线程的 RunLoop 需要手动开启,否知无法监听到输入源与定时源。子线程 RunLoop 随着所在子线程的事件源结束而关闭,随着所在子线程的结束而释放。
获取/创建RunLoop对象
NSRunLoop 对象不是线程安全,如果在不同线程使用同一个 RunLoop 对象,可以用 CFRunLoopRef,保证线程安全。
[NSRunLoop currentRunLoop]; // 当前线程的runLoop
[NSRunLoop mainRunLoop]; // 主线程runLoop
CFRunLoopGetCurrent();
CFRunLoopGetMain();
[NSRunLoop currentRunLoop].getCFRunLoop; //NSRunLoop转CFRunLoopRef
添加定时器及输入源
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
CFRunLoopAddTimer(runLoopRef, runLoopTimerRef, kCFRunLoopDefaultMode);
CFRunLoopAddSource(runLoopRef, runLoopSourceRef, kCFRunLoopDefaultMode);
启动RunLoop
[[NSRunLoop currentRunLoop] run]; // 无条件且以默认的NSDefaultRunLoopMode启动
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; // 指定过期时间且以默认的NSDefaultRunLoopMode启动
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; // 指定过期时间,指定启动方式
CFRunLoopRun(); // 子线程的runLoop需要启动
CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeInterval, returnAfterSourceHandled);
退出RunLoop
子线程的 RunLoop 会随着事件源的结束而 Exit,所以一般不会主动去停止 RunLoop。
/* 给RunLoop设置超时时间 */
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:2.0]]; //指定过期时间且以默认的NSDefaultRunLoopMode启动
/* 通知RunLoop停止 */
CFRunLoopStop([NSRunLoop currentRunLoop].getCFRunLoop);
主线程场景
可直接使用 timer,默认为 NSDefaultRunLoopMode。
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(fireDemo) userInfo:nil repeats:YES];
UIScrollView 滑动时 RunLoop 切到了 UITrackingRunLoopMode 模式,默认为 NSDefaultRunLoopMode 的 Timer 不会调用,设置为 NSRunLoopCommonModes 就可以恢复正常。
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
子线程场景
开启子线程 RunLoop 时必须添加相应的 timer 或 port,否知运行无效。子线程 RunLoop 启动时,runMode 之后的代码不会执行,直到 RunLoop 随着 Timer 结束而 Exit 后,才执行 runMode 之后的代码;timer 的 repeats 为 YES 时,不会执行 runMode 之后的代码;子线程 RunLoop 没有 UITrackingRunLoopMode 模式。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerCount) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; //添加timer,使用默认runloop模式
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
// timer结束后,触发breakPoint
});
一旦调用 [currentRunLoop run] 这个方法开启子线程的运行循环就不能停止,阻塞 run 之后代码执行,子线程也不会被释放。
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerCount) userInfo:nil repeats:NO];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
// timer结束后,没有触发breakPoint
});
调用 runUntilDate 会阻塞之后的代码执行,直到指定的时间过后才恢复执行。
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];
// do something
创建常驻子线程
+ (NSThread *)networkThread {
static dispatch_once_t onceToken;
static NSThread *_networkThread;
dispatch_once(&onceToken, ^{
_networkThread = [[NSThread alloc] initWithTarget:[[self class] sharedInstance] selector:@selector(_runLoopThread) object:nil];
[_networkThread setName:@"com.network.request"];
[_networkThread setQualityOfService:[[NSThread mainThread] qualityOfService]];
[_networkThread start];
});
return _networkThread;
}
- (void)_runLoopThread {
// 直接使用run比较暴力,无法手动销毁线程
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
/* 另一种形式
while (!_stopRunning) {
@autoreleasepool {
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
*/
}
同步等待
在主线程中可以用 RunLoop 实现同步等待的操作,不会造成线程的阻塞,实际应用中存在代码中断执行的情况,等待异步的执行结果可以使用信号量 wait-signal。
__block BOOL isContinue = NO;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
isContinue = YES;
});
while (isContinue == NO) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
// isContinue为YES后执行后续代码