没有runloop 就意味着app一运行就会退出(换句话说,runloop保护着app不会被退出)
- NSRunLoop - Foundation 框架
- CFRunLoop - CoreFoundation 框架下 (NSRunloop就是CFRunloop的封装)
下载地址: https://opensource.apple.com/tarballs/CF/CF-1151.16.tar.gz
RunLoop和线程的关系
- 和线程是1对1的(存在于一个全局的map中, 线程作为key, runLoop 作为value)
- 线程中默认是没有runloop的(主线程在AppDelegate中会自动获取(创建) )
- 通过
[NSRunLoop currentRunLoop];
创建runLoop
; - 通过
[NSRunLoop mainRunLoop];
获取主线程runLoop
; - Runloop会在线程结束的时候销毁
RunLoop创建源码分析
- 注意阅读注释
[CFRunLoop currentRunLoop];
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFLock_t loopsLock = CFLockInit;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
// t : 传入的当前线程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
// __CFRunLoops 静态map 初始化为null
if (!__CFRunLoops) {
// 创建全局 runloopMap和主线程的runloop,
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
// 将 dict 赋值给__CFRunLoops (搞不懂__CFRunLoops的可以看这里)
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 使用 入参的 t 在 全局的map中 获取到响应的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
// 如果没有获取到runloop, 则新建一个runloop
if (!loop) {
// 新建一个
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
// 再次获取当先线程runloop
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
// 确定当前线程没有runloop 将上面新创建的newloop保存到全局的map中
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
可以自己创建监听来监听Runloop的状态
// 通过block的方式回调
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"######进入kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"######即将处理Timer事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"######即将处理Source事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"######即将休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"######被唤醒");
break;
case kCFRunLoopExit:
NSLog(@"######退出RunLoop");
break;
default:
break;
}
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"当前runloop模式 - %@", mode);
CFRelease(mode);
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
RunLoop的应用
定时器失效
Q: 定时器失效问题
A: 因为timer模式是工作在runloop的kCFRunLoopDefaultMode
模式下,
当滑动scorllView
时候runloop会切换到UITrackingRunLoopMode
模式下,所以导致timer定时器失效;
解决方法
// 可以将timer标记为CommonModes
[NSRunLoop currentRunLoop]addTimer:timer forMode:kCFRunLoopCommonModes];
线程保活
需要保护线程不退出可以给子线程添加runloop来保证线程不被退出
如果需要在适当的时候销毁线程 runloop 一定不能通过 [runloop run]
方法开启
PS: runloop提供的run方法是无法销毁的,其专门用于开启一个永不销毁的线程
如果需要在适当的时候销毁子线程, 就需要自己实现runloop的开启方法
// 这一句 是在dealloc中执行stop函数时防止对象已经被释放导致weakSelf为nil, 判断失效导致runloop无法销毁
// while(weakSelf && !wealSelf.isStop)
while(!isStop) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
// 当需要结束线程的时候 在相关的方法中 将 stop 标记为no 用以结束runloop 从而退出线程
-(void)stop{
// 注意! waitUntilDone 参数需要设置为YES, 不然如果在dealloc中执行stop函数 waitUntilDone设置为NO的话会进行异步操作导致野指针
[self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
}
-(void)stopThread{
isStop = false;
CFRunLoopStop(CFRunLoopGetCurrent())
}