一、RunLoop 本质
RunLoop就是一个运行循环,在每次循环中接受消息,处理消息,然后休眠或者进入下一次循环。
RunLoop底层就是一个while循环;
官网的解释是:在2-9之间循环
休眠:该线程会释放占用的所有资源(从CPU任务队列里移除),系统会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程或者系统才能被唤醒。
二、RunLoop底层结构
RunLoop也是一个对象,底层也是一个结构体struct __CFRunLoop(如下图)。
具有以下特征:
1、一个RunLoop包含一个或者多个Mode,每个Mode又包含Source0/Source1/Timer/Observer
2、开启RunLoop时,只能从Modes集合CFMutableSetRef中选择一个Mode作为当前的currentMode启动,如果遇到切换Mode(例如页面滑动,活动停止时),要退出当前Loop,再重新选择一个Mode进入
3、如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
runloop 部分源码
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
//通知Observer进入runloop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//处理事件
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//通知Observer 退出runloop
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
通过源码可以窥探一二
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm...) {
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
__CFRunLoopDoBlocks(rl, rlm);
__CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
__CFRunLoopDoBlocks(rl, rlm);
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
do {
__CFRunLoopServiceMachPort(...);
if (....) { break;}
} while (1);
__CFRunLoopDoBlocks(rl, rlm);
retVal = kCFRunLoopRunStopped;
} while (0 == retVal);
return retVal;
}
三、线程与RunLoop
线程中没有RunLoop,那么该线程执行完任务就会销毁。
如下代码,观察执行结果:
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull t) {
NSLog(@"---------%@",[[NSRunLoop currentRunLoop] currentMode]);
}];
NSLog(@"-----end----");
});
打印如下
Test_06_Runloop[96546:11552658] -----end----
发现根本不会执行timer中的任务,因为线程已经销毁了。而NSTimer的使用官方也说了,必须依赖RunLoop(NSTimer底层就是通过RunLoop实现)。
所以这时必须要让线程中的RunLoop 运行run起来才能执行timer中的任务
注意:NSTimer可以自动添加到RunLoop中,所以[loop addTimer: forMode:]可以省略,但是[loop run]或者[loop runMode:beforeDate:]必须执行才能让RunLoop运行起来
换成如下代码
dispatch_async(dispatch_get_global_queue(0, 0), ^{
//不能写在timer创建之前,不然RunLoop运行时,其中没有事件源,也会立马退出
//[[NSRunLoop currentRunLoop] run];
NSTimer*timer = [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull t) {
NSLog(@"---------%@",[[NSRunLoop currentRunLoop] currentMode]);
}];
//可写可不写,因为timer自动添加到RunLoop,如果RunLoop存在
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//[[NSRunLoop currentRunLoop] run]不能退出,后面会说到,不急
//可以控制RunLoop是否退出,
while (!_stop) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
NSLog(@"-----end----");
});
打印如下:线程没有退出,timer正常执行
Test_06_Runloop[97491:11937784] ---------kCFRunLoopDefaultMode
Test_06_Runloop[97491:11937784] ---------kCFRunLoopDefaultMode
Test_06_Runloop[97491:11937784] ---------kCFRunLoopDefaultMode
Test_06_Runloop[97491:11937784] ---------kCFRunLoopDefaultMode
四、线程保活
开启RunLoop的几种方法
- (void)run;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
说明一下,run方法内部其实大概就是不断调用:runMode:beforeDate:
while (condition) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
线程start时,在线程内部添加一个RunLoop,并且保证添加的RunLoop有source/timer/observer其中的一种,不然RunLoop会立马退出。要想控制线程的生命周期,不能用run方法,要用runMode:beforeDate:方法,因为官方说的:run()方法一旦开启RunLoop,那么将不会被打断(If you want the run loop to terminate, you shouldn’t use this method. ),贴心的官方还给出示例:
BOOL shouldKeepRunning = YES; // global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (shouldKeepRunning && [theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]);
以下代码可以让线程休眠,不退出。
self.thread =[[NSThread alloc] initWithBlock:^{
_shouldKeepRunning = YES;// global
NSRunLoop *theRL = [NSRunLoop currentRunLoop];
while (_shouldKeepRunning){
[theRL runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
};
}];
[self.thread start];
需要用子线程执行任务时,可以调用
[self performSelector:@selector(doSomethingInSubThread:) onThread:self.thread withObject:@"sdf" waitUntilDone:NO];
想要线程停止,需要先关闭RunLoop
注意: CFRunLoopStop()方法是停止当前那一次循环,想要彻底停止,那么需要添加标志位_shouldKeepRunning 去判断
- (void)stopSubThread {
CFRunLoopStop(CFRunLoopGetCurrent());
}
- (void)dealloc {
_shouldKeepRunning = NO;
[self performSelector:@selector(stopSubThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}
五、监测observer的状态
observer可以监测到RunLoop的mode的切换,
比如页面滑动时先kCFRunLoopExit 当前的NSDefaultRunLoopMode,然后再kCFRunLoopEntry一个新的UITrackingRunLoopMode
CFRunLoopObserverRef runloopObsever=CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry - %@",mode);
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers - %@",mode);
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources - %@",mode);
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting - %@",mode);
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting - %@",mode);
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit - %@",mode);
break;
default:
break;
}
CFRelease(mode);
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), runloopObsever, kCFRunLoopCommonModes);
CFRelease(runloopObsever);
其中部分打印
Test_06_Runloop[97611:11941654] kCFRunLoopAfterWaiting - kCFRunLoopDefaultMode //唤醒
Test_06_Runloop[97611:11941654] kCFRunLoopBeforeTimers - kCFRunLoopDefaultMode //处理timer
Test_06_Runloop[97611:11941654] kCFRunLoopBeforeSources - kCFRunLoopDefaultMode //处理source
Test_06_Runloop[97611:11941654] kCFRunLoopBeforeWaiting - kCFRunLoopDefaultMode //没事可做了,处理完当前任务后休眠