RunLoop的定义与概念html
RunLoop的主要做用objective-c
main函数中的RunLoop编程
RunLoop与线程的关系swift
RunLoop的消息种类数组
RunLoop的对外接口安全
RunLoop的modebash
RunLoop的内部逻辑markdown
RunLoop在iOS中内部的应用网络
RunLoop在iOS开发中的实践app
总结
参考文献
RunLoop
, 就是一个在Run的loop,就是一个一直在跑的圈。其本质就是无休止的while循环
。通常的程序都是执行完任务后便结束。但因为手机应用的特殊性,在其不执行任务时,也不能将其杀死,而是暂时休眠状态,直到有外部或内部因素将其唤醒,继续run。直到用户手动将该程序完全关闭。// 无Runloop,程序执行完后,直接返回
int main(int argc,char * argv[]){
NSLog(@"execute main function");---->程序开始
return 0; ------------------------->程序结束
}
// 有Runloop
int main(int argc,char * argv[]){
BOOL running = YES; -------->程序开始
do {------------------------------
// 执行各类任务,处理各类事件------持续运行
}while(running);---------------------
return 0;
}
复制代码
NSRunLoop
和 CFRunLoopRef
。
CFRunLoopRef
是在 CoreFoundation
框架内的,它提供了纯 C 函数的 API,全部这些 API 都是线程安全的
。CFRunLoopRef
的封装,提供了面向对象的 API,可是这些 API 不是线程安全的
。main
函数内部也启动了一个RunLoopint main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
复制代码
UIApplicationMain
函数内部帮咱们开启了主线程的 RunLoop
, UIApplicationMain
内部拥有一个无线循环的代码。这个 UIApplicationMain
函数帮咱们启动的 RunLoop 属于程序的主线程全部,因此咱们不须要再为主线程开启 RunLoop。因为咱们的程序在主线程上拥有一个 RunLoop ,因此咱们将程序打开后,执行完它所须要的任务后,不必定非要退出程序,而是能够选择将其后台挂起。一切都是由于 主线程上RunLoop 的关系,咱们的程序才能够长时间持续运行。获取
方法,就不会得到 RunLoop 的缘由。在 Core Foundation 中
在 Cocoa 中
CFRunLoopRef
和 NSRunLoop
能够转化, NSRunLoop
使用 getCFRunLoop
方法就能够获得 CFRunLoopRef
对象
/// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef static CFMutableDictionaryRef loopsDic; /// 访问 loopsDic 时的锁 static CFSpinLock_t loopsLock; /// 获取一个 pthread 对应的 RunLoop。 CFRunLoopRef _CFRunLoopGet(pthread_t thread) { OSSpinLockLock(&loopsLock); if (!loopsDic) { // 第一次进入时,初始化全局Dic,并先为主线程建立一个 RunLoop。 loopsDic = CFDictionaryCreateMutable(); CFRunLoopRef mainLoop = _CFRunLoopCreate(); CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop); } /// 直接从 Dictionary 里获取。 CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread)); if (!loop) { /// 取不到时,建立一个 loop = _CFRunLoopCreate(); CFDictionarySetValue(loopsDic, thread, loop); /// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。 _CFSetTSD(..., thread, loop, __CFFinalizeRunLoop); } OSSpinLockUnLock(&loopsLock); return loop; } CFRunLoopRef CFRunLoopGetMain() { return _CFRunLoopGet(pthread_main_thread_np()); } CFRunLoopRef CFRunLoopGetCurrent() { return _CFRunLoopGet(pthread_self()); } 复制代码
这是apple官方文档的一张图, 表示了Runloop的消息种类。
这张图我也没有特别看懂,主要是讲的Runloop的两种输入源。官方文档的解释是
CFRunLoopSourceSignal(source)
,将这个 Source 标记为待处理,而后手动调用 CFRunLoopWakeUp(runloop)
来唤醒 RunLoop,让其处理这个事件。typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即将进入Loop kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠 32 kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒 64 kCFRunLoopExit = (1UL << 7), // 即将退出Loop 128 }; 复制代码
struct __CFRunLoopMode { CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode" CFMutableSetRef _sources0; // Set CFMutableSetRef _sources1; // Set CFMutableArrayRef _observers; // Array CFMutableArrayRef _timers; // Array ... }; struct __CFRunLoop { CFMutableSetRef _commonModes; // Set CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer> CFRunLoopModeRef _currentMode; // Current Runloop Mode CFMutableSetRef _modes; // Set ... }; 复制代码
系统默认注册了5个Mode:
咱们平时主要应用的 mode 有 kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
两种, 其中 kCFRunLoopDefaultMode(在cocoa中也叫NSDefaultRunLoopMode)
是咱们开启一个 RunLoop 时默认的 mode 方式。 而 UITrackingRunLoopMode
主要是追踪 ScrollView 滑动时的状态。
这里有个概念叫 “CommonModes”:一个 Mode 能够将本身标记为”Common”属性(经过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具备 “Common” 标记的全部Mode里。须要注意的是: “CommonModes”并非一个真正的 mode , 它其实是一个 数组 里面放入了 kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
应用场景举例:主线程的 RunLoop 里有两个预置的 Mode:kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
。这两个 Mode 都已经被标记为”Common”属性。DefaultMode
是 App 平时所处的状态,UITrackingRunLoopMode
是追踪 ScrollView 滑动时的状态。当你建立一个 Timer 并加到 DefaultMode
时,Timer 会获得重复回调,但此时滑动一个TableView时,RunLoop 会将 mode 切换为 UITrackingRunLoopMode
,这时 Timer 就不会被回调,而且也不会影响到滑动操做。有时你须要一个 Timer,在两个 Mode 中都能获得回调,一种办法就是将这个 Timer 分别加入这两个 Mode。还有一种方式,就是将 Timer 加入到顶层的 RunLoop 的 “commonModeItems” 中。”commonModeItems” 被 RunLoop 自动更新到全部具备”Common”属性的 Mode 里去。
CFRunLoop对外暴露的管理 Mode 接口只有下面2个:
CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
CFRunLoopRunInMode(CFStringRef modeName, ...);
复制代码
CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
复制代码
先放一张我认为很是好的图,其中仍是有些错误之处,根据官方文档,第7步:休眠,等待唤醒的source种类应该是source1,而不是source0。
内部代码实现以下 若是嫌太长,直接看注释便可。
/// 用DefaultMode启动 void CFRunLoopRun(void) { CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); } /// 用指定的Mode启动,容许设置RunLoop超时时间 int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) { return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled); } /// RunLoop的实现 int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) { /// 首先根据modeName找到对应mode CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false); /// 若是mode里没有source/timer/observer, 直接返回。 if (__CFRunLoopModeIsEmpty(currentMode)) return; /// 1. 通知 Observers: RunLoop 即将进入 loop。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry); /// 内部函数,进入loop __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) { Boolean sourceHandledThisLoop = NO; int retVal = 0; do { /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers); /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources); /// 执行被加入的block __CFRunLoopDoBlocks(runloop, currentMode); /// 4. RunLoop 触发 Source0 (非port) 回调。 sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle); /// 执行被加入的block __CFRunLoopDoBlocks(runloop, currentMode); /// 5. 若是有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 而后跳转去处理消息。 if (__Source0DidDispatchPortLastTime) { Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg) if (hasMsg) goto handle_msg; } /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。 if (!sourceHandledThisLoop) { __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting); } /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。 /// • 一个基于 port 的Source 的事件。 /// • 一个 Timer 到时间了 /// • RunLoop 自身的超时时间到了 /// • 被其余什么调用者手动唤醒 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) { mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg } /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。 __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting); /// 收到消息,处理消息。 handle_msg: /// 9.1 若是一个 Timer 到时间了,触发这个Timer的回调。 if (msg_is_timer) { __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time()) } /// 9.2 若是有dispatch到main_queue的block,执行block。 else if (msg_is_dispatch) { __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); } /// 9.3 若是一个 Source1 (基于port) 发出事件了,处理这个事件 else { CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort); sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg); if (sourceHandledThisLoop) { mach_msg(reply, MACH_SEND_MSG, reply); } } /// 执行加入到Loop的block __CFRunLoopDoBlocks(runloop, currentMode); if (sourceHandledThisLoop && stopAfterHandle) { /// 进入loop时参数说处理完事件就返回。 retVal = kCFRunLoopRunHandledSource; } else if (timeout) { /// 超出传入参数标记的超时时间了 retVal = kCFRunLoopRunTimedOut; } else if (__CFRunLoopIsStopped(runloop)) { /// 被外部调用者强制中止了 retVal = kCFRunLoopRunStopped; } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) { /// source/timer/observer一个都没有了 retVal = kCFRunLoopRunFinished; } /// 若是没超时,mode里没空,loop也没被中止,那继续loop。 } while (retVal == 0); } /// 10. 通知 Observers: RunLoop 即将退出。 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); } 复制代码
{ /// 1. 通知Observers,即将进入RunLoop /// 此处有Observer会建立AutoreleasePool: _objc_autoreleasePoolPush(); __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry); do { /// 2. 通知 Observers: 即将触发 Timer 回调。 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers); /// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources); __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); /// 4. 触发 Source0 (非基于port的) 回调。 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0); __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block); /// 6. 通知Observers,即将进入休眠 /// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush(); __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting); /// 7. sleep to wait msg. mach_msg() -> mach_msg_trap(); /// 8. 通知Observers,线程被唤醒 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting); /// 9. 若是是被Timer唤醒的,回调Timer __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer); /// 9. 若是是被dispatch唤醒的,执行全部调用 dispatch_async 等方法放入main queue 的 block __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block); /// 9. 若是若是Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1); } while (...); /// 10. 通知Observers,即将退出RunLoop /// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop(); __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit); } 复制代码
__IOHIDEventSystemClientQueueCallback()
。_UIApplicationHandleEventQueue()
进行应用内部的分发。_UIApplicationHandleEventQueue()
会把 IOHIDEvent 处理并包装成 UIEvent 进行处理或分发,其中包括识别 UIGesture/处理屏幕旋转/发送给 UIWindow 等。一般事件好比 UIButton 点击、touchesBegin/Move/End/Cancel 事件都是在这个回调中完成的。__IOHIDEventSystemClientQueueCallback()
内触发的 Source0,Source0 再触发的 _UIApplicationHandleEventQueue()
。因此 UIButton 事件看到是在 Source0 内的。_UIApplicationHandleEventQueue()
识别了一个手势时,其首先会调用 Cancel 将当前的 touchesBegin/Move/End 系列回调打断。随后系统将对应的 UIGestureRecognizer 标记为待处理。 苹果注册了一个 Observer 监测 BeforeWaiting (Loop即将进入休眠) 事件,这个Observer的回调函数是 _UIGestureRecognizerUpdateObserver()
,其内部会获取全部刚被标记为待处理的 GestureRecognizer,并执行GestureRecognizer的回调。 当有 UIGestureRecognizer 的变化(建立/销毁/状态改变)时,这个回调都会进行相应处理。_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()
。这个函数里会遍历全部待处理的 UIView/CAlayer 以执行实际的绘制和调整,并更新 UI 界面。__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其余线程仍然是由 libDispatch 处理的。+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } + (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; } 复制代码
利用PerformSelector设置当前线程的RunLoop的运行模式
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"tupian"] afterDelay:4.0 inModes:NSDefaultRunLoopMode]; 复制代码
解释上述 AFNetworking 长时间链接问题 。
方法1即 AFNetworking 中使用的方法,它使得 thread 在打开后,使用结束方法CFRunLoopStop(CFRunLoopGetCurrent())
并无正常结束,该结束方法对这个建立 Thread 的方式无效。因此致使了内存暴增,图以下
方法2 如图,因为它是一个非线程阻塞的方法,因此有时在线程还已经退出后,才开始暂停这个线程,天然会使得程序崩溃。
方法3 才是我推荐的方法 ,它能够正确的建立一个后台常驻线程。
下面解释一下原理吧
CFRunLoopStop()
方法只会结束当前的 runMode:beforeDate: 调用,而不会结束后续的调用。run()
方法,实际上就是在内部不断地调用runMode:beforeDate:
方法,而所谓的runUntilDate:
方法,也是有限的调用runMode:beforeDate:
方法。只是它们所传的参数都是DefaultMode
罢了。CFRunLoopStop()
方法手动将 RunLoop 结束。CFRunLoopStop()
方法手动将 RunLoop 结束。CFRunLoopStop()
方法的定义中说了。The difference is that you can use this technique on run loops you started unconditionally.
If you want the run loop to terminate, you shouldn't use this method
runMode:beforeDate:
调用,而不会结束后续的调用。这也就是为何 Runloop 的文档中说CFRunLoopStop()
能够 exit(退出) 一个 RunLoop,而在 run 等方法的文档中又说这样会致使 RunLoop 没法 terminate(终结)。