前言
Runloop是ios应用于线程中的一种循环机制。系统本身没有创建runloop的API,不过可以通过currentRunLoop获取当前RunLoop。主线程本身就存在一个runloop,而且是运行转态,子线程的runLoop需要手动开启,否知无法监听到输入源与定时源。子线程RunLoop随着所在子线程的事件源结束而关闭,随着所在子线程的结束而释放。
获取/创建RunLoop对象
[NSRunLoop currentRunLoop];//当前线程的runLoop
[NSRunLoop mainRunLoop];//主线程runLoop
CFRunLoopGetCurrent();
CFRunLoopGetMain();
[NSRunLoop currentRunLoop].getCFRunLoop; //NSRunLoop转CFRunLoopRef
NSRunLoop对象不是线程安全,如果在不同线程使用同一个runLoop对象,可以用CFRunLoopRef,保证线程安全。
添加定时器及输入源
[[NSRunLoop currentRunLoop] addTimer:<#(nonnull NSTimer *)#> forMode:<#(nonnull NSRunLoopMode)#>]
[[NSRunLoop currentRunLoop] addPort:<#(nonnull NSPort *)#> forMode:<#(nonnull NSRunLoopMode)#>]
CFRunLoopAddTimer(<#CFRunLoopRef rl#>, <#CFRunLoopTimerRef timer#>, <#CFRunLoopMode mode#>)
CFRunLoopAddSource(<#CFRunLoopRef rl#>, <#CFRunLoopSourceRef source#>, <#CFRunLoopMode mode#>)
启动RunLoop
[[NSRunLoop currentRunLoop] run]; //无条件且以默认的NSDefaultRunLoopMode启动
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate new]]; //指定过期时间且以默认的NSDefaultRunLoopMode启动
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate new]];//指定过期时间,指定启动方式
CFRunLoopRun(); //子线程的runLoop需要启动
CFRunLoopRunInMode(<#CFRunLoopMode mode#>, <#CFTimeInterval seconds#>, <#Boolean returnAfterSourceHandled#>)
退出RunLoop
/* 给RunLoop设置超时时间 */
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate new]]; //指定过期时间且以默认的NSDefaultRunLoopMode启动
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate new]];//指定过期时间,指定启动方式
/* 通知RunLoop停止 */
CFRunLoopStop([NSRunLoop currentRunLoop].getCFRunLoop);
子线程的RunLoop会随着事件源的结束而exit,所以一般不会主动去停止Runloop。
主线程场景
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(fireDemo) userInfo:nil repeats:YES];
可直接使用timer,默认为NSDefaultRunLoopMode。
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
UIScrollView滑动时RunLoop切到了UITrackingRunLoopMode模式,默认为NSDefaultRunLoopMode的Timer不会调用,设置为NSRunLoopCommonModes就可以恢复正常。
子线程场景
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
});
子线程开启RunLoop的代码,runLoop启动时,runMode之后的代码不会执行,直到RunLoop随着Timer结束而exit后,才执行runMode之后的代码;timer的repeats为YES时,不会执行runMode之后的代码;子线程RunLoop没有UITrackingRunLoopMode模式。
//开启子线程runloop时必须添加相应的timer或port,否知运行无效
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]];
[[NSRunLoop currentRunLoop] 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
});
一旦调用[currentRunLoop run]这个方法开启子线程的运行循环就不能停止,阻塞run之后代码执行,子线程也不会被释放。
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3.0]];
//do something
在run之后会阻塞代码,等待runLoop结束后才会执行。
创建常驻子线程
+ (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];
/* 另一种形式
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
while (!_stopRunning) {
@autoreleasepool {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}
*/
}
阻塞代码但不阻塞线程
__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后执行后续代码
实际项目中经常在主线程用该方法实现阻塞代码但不阻塞线程操作,例如:等某个异步操作完成后再调用后续的代码块。