大家好,我是OB! 今天来聊聊RunLoop!
RunLoop就是一个运行循环,在每次循环中接受消息,处理消息,然后休眠或者进入下一次循环。
RunLoop底层就是一个while循环;
官网的解释是:在2-9之间循环
休眠:该线程会释放占用的所有资源(从CPU任务队列里移除),系统会把该线程放入“等待池”中。进入这个状态后,是不能自动唤醒的,必须依靠其他线程或者系统才能被唤醒。
RunLoop也是一个对象,底层也是一个结构体struct __CFRunLoop
(如下图)。
具有以下特征:
RunLoop
包含一个或者多个Mode
,每个Mode
又包含Source0
/Source1
/Timer
/Observer
RunLoop
时,只能从Modes
集合CFMutableSetRef
中选择一个Mode
作为当前的currentMode
启动,如果遇到切换Mode
(例如页面滑动,活动停止时),要退出当前Loop
,再重新选择一个Mode
进入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,那么该线程执行完任务就会销毁。
如下代码,观察执行结果:
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
可以监测到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 //没事可做了,处理完当前任务后休眠
今天先到这里,下期见!欢迎留言!