从源码看Runloop-内部逻辑
RunLoop的入口
NSRunLoop有三个启动runloop的方法,CFRunLoop有两个启动方法。代码如下
// NSRunLoop 3个入口方法
- (void)run;
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSRunLoopMode)mode beforeDate:(NSDate *)limitDate;
由于NSRunLoop看不到其实现,我们以CF代码分析。
// CFRunLoop 2个入口方法
// 方法1实现
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
kCFRunLoopDefaultMode,
1.0e10,
false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// 方法2实现
SInt32 CFRunLoopRunInMode(CFStringRef modeName,
CFTimeInterval seconds,
Boolean returnAfterSourceHandled) {
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(),
modeName,
seconds,
returnAfterSourceHandled);
}
可以看到CFRunLoop的两个启动方法其真正干活的是CFRunLoopRunSpecific方法。而该方法实现如下:
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl,
CFStringRef modeName,
CFTimeInterval seconds,
Boolean returnAfterSourceHandled) {
// // 省略一堆准备工作...
if (currentMode->_observerMask & kCFRunLoopEntry ){
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
}
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
if (currentMode->_observerMask & kCFRunLoopExit ) {
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
// 省略一堆接收工作...
return result;
}
在这个方法中才到了CFRunLoop的真正入口:__CFRunLoopRun。注意参数returnAfterSourceHandled,它为YES时,表示Runloop每处理完一个事件后都会返回。
runloop中一共有7个通知事件,如下代码所示。kCFRunLoopAllActivities是所有事件的集合,不是一个真正的事件。还剩6个事件,其中进入和退出是CFRunLoopRunSpecific方法中发送的,如上代码所示每次进入runloop前和结束runloop后都会会通知当前mode对象中的observer。其他4个是在__CFRunLoopRun方法中发送的。
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5),
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
如上面提到的returnAfterSourceHandled,在NSRunLoop的三个入口方法中,经验证得到,该参数是YES。因为采用NSRunLoop的方法每处理完一个事件runloop都会返回。
runloop内部处理逻辑
runloop内部逻辑如下图所示:
先连发两个通知,告诉observer要处理timer和source0了。
这里不清楚的是为什么要把source0和source1分开?难道是source0不能唤醒runloop?
经过试验得到performSelector是在source0中执行的。通常情况下我们唤醒runloop的方式就只要timer和performSeletor和GCD。创建source0的情况我们能用到的就是socket。
runloop会在一次循环中把积压的所有事件都做完。
AutoReleasePool
App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。