iOS之深入解析Runloop的底层原理

Posted Forever_wj

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS之深入解析Runloop的底层原理相关的知识,希望对你有一定的参考价值。

一、Runloop 简介

① 什么是 Runloop ?
  • RunLoop 是事件接收和分发机制的一个实现,是线程相关的基础框架的一部分,一个 RunLoop 就是一个事件处理的循环,用来不停的调度工作以及处理输入事件。
  • Runloop 不仅仅是一个运行循环(do-while 循环),也是提供了一个入口函数的对象,消息机制处理模式,运行循环从两种不同类型的源接收事件。
  • 输入源提供异步事件,通常是来自另一个线程或来自不同应用程序的消息。定时器源提供同步事件,发生在预定时间或重复间隔。
  • 两种类型的源都使用特定于应用程序的处理程序例程来处理事件。除了处理输入源之外,Runloop 还会生成有关 Runloop 行为的通知。
  • 已注册的运行循环观察器可以接收这些通知并使用它们在线程上执行其他处理。
② Runloop 作用
  • 当启动一个 ios APP 时主线程启动与其对应的 RunLoop 也已经开启,如果不杀掉 APP 则会一直运行,就是因为 RunLoop 循环一直为开启状态保证主线程不会被摧毁,这也是 RunLoop 的作用之一:保证线程不退出,保持程序的持续运行
  • RunLoop 在循环过程中监听事件,当前线程有任务时,唤醒当当前线程去执行任务,任务执行完成以后,使当前线程进入休眠状态。当然这里的休眠不同于我们写的死循环 while(1),它在休眠时几乎不会占用系统资源,这是由操作系统内核去负责实现的。
  • 处理 App 中的各种事件(触摸、定时器、performSelector):UIApplicationMain() 函数方法会默认为主线程设置一个 NSRunLoop 对象,这个循环会随时监听屏幕上由用户触摸所带来的底层消息并将其传递给主线程去处理,当点击一个 button 事件的传递从图上的调用栈可以看出(监听的范围还包含时钟/网络)。
  • RunLoop 循环与 While 循环的区别在于,RunLoop 会在没有事件发生时进入休眠状态从而不占用 CPU 消耗,有事件发生才会去找对应的 Handler 处理事件,而 while 则会一直占用,从而节省 CPU 的资源,提高程序的性能(需要执行事务就执行,不需要就休眠)
  • 在 Cocoa 程序的线程中都可以通过代码 NSRunLoop *runloop = [NSRunLoop currentRunLoop] 来获取到当前线程的 Runloop 对象。
③ Runloop 应用
  • block 应用:CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
  • 调用 timer:CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
  • 响应 source0:CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
  • 响应 source1: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
  • GCD 主队列:CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
  • observer 源:CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION

二、Runloop 与线程

① Runloop 与线程的关系
  • Runloop 与线程是一一对应的:一个 Runloop 对应一个核心的线程, Runloop 是可以嵌套的,但是核心的只能有一个,它们的关系保存在一个全局的字典里。
  • Runloop 是用来管理线程的:当线程的 Runloop 被开启后,线程会在执行完任务后进入休眠状态,有了任务就会被唤醒去执行任务。
  • Runloop 在第一次获取时被创建,在线程结束时被销毁。
  • 对于主线程来说,Runloop 在程序一启动就被默认创建好。
  • 对于子线程来说,Runloop 是懒加载的,只有当我们使用的时候才会创建,所以在子线程用定时器要注意,确保子线程的 Runloop 被创建,不然定时器不会回调。
  • 实例分析:
    • 如下所示,程序启动创建了一个子线程,在子线程内添加了一个定时器 timer,并开启子线程的 runLoop,开始打印 hello word,当点击屏幕时退出子线程停止打印:
	- (void)viewDidLoad {
	    [super viewDidLoad];
	        
	    self.isStopping = NO;
	    NSThread *customThread = [[NSThread alloc] initWithBlock:^{
	        NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]);
	        [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
	            NSLog(@"hello word");     
	            if (self.isStopping) {
	                [NSThread exit];
	            }
	        }];
	        [[NSRunLoop currentRunLoop] run];
	    }];
	    customThread.name = @"customThread";
	    [customThread start];
	}
	
	- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
	    self.isStopping = YES;
	}
    • 打印结果如下:
	<NSThread: 0x600001fb21c0>{number = 3, name = customThread}---customThread
	hello word
	hello word
	hello word
	hello word
	hello word
	hello word
	hello word
    • 程序启动,通过 isStopping 变量来控制当前线程,线程控制 runloop,runloop 控制 timer。注意:子线程 runloop 默认不开启,timer 依赖于 runloop。
② Runloop 与线程的底层分析
  • 一般在日常开发中,RunLoop 的获取有以下两种方式:
	// 主运行循环
	CFRunLoopRef mainRunloop = CFRunLoopGetMain();
	// 当前运行循环
	CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
  • 进入 CFRunLoopGetMain 源码,如下所示:
	CFRunLoopRef CFRunLoopGetMain(void) {
	    CHECK_FOR_FORK();
	    static CFRunLoopRef __main = NULL; // no retain needed
	    // pthread_main_thread_np 主线程
	    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
	    return __main;
	}
  • 进入 _CFRunLoopGet0:
	// should only be called by Foundation
	// t==0 is a synonym for "main thread" that always works
	CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
	    // 如果t不存在,则标记为主线程(即默认情况,默认是主线程)
	    if (pthread_equal(t, kNilPthreadT)) {
	        t = pthread_main_thread_np();
	    }
	    __CFSpinLock(&loopsLock);
	    if (!__CFRunLoops) {
	        __CFSpinUnlock(&loopsLock);
	        
	        // 创建全局字典,标记为kCFAllocatorSystemDefault
	        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
	        // 通过主线程 创建主运行循环
	        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
	        // 利用dict,进行key-value绑定操作,即可以说明,线程和runloop是一一对应的
	        // dict : key value
	        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
	        
	        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
	            CFRelease(dict);
	        }
	        
	        CFRelease(mainLoop);
	        __CFSpinLock(&loopsLock);
	    }
	    // 通过其他线程获取runloop
	    CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
	    __CFSpinUnlock(&loopsLock);
	    if (!loop) {
	        // 如果没有获取到,则新建一个运行循环
	        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
	        __CFSpinLock(&loopsLock);
	        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
	        if (!loop) {
	            // 将新建的runloop与线程进行key-value绑定
	            CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
	            loop = newLoop;
	        }
	        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
	        __CFSpinUnlock(&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 创建

① 底层分析
  • 进入 __CFRunLoopCreate 源码,主要是对 runloop 属性的赋值操作:
	static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
	    CFRunLoopRef loop = NULL;
	    CFRunLoopModeRef rlm;
	    uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
	    loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL);
	    // 如果loop为空,则直接返回NULL
	    if (NULL == loop) {
	        return NULL;
	    }
	    // runloop属性配置
	    (void)__CFRunLoopPushPerRunData(loop);
	    __CFRunLoopLockInit(&loop->_lock);
	    loop->_wakeUpPort = __CFPortAllocate();
	    if (CFPORT_NULL == loop->_wakeUpPort) HALT;
	    __CFRunLoopSetIgnoreWakeUps(loop);
	    loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	    CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode);
	    loop->_commonModeItems = NULL;
	    loop->_currentMode = NULL;
	    loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	    loop->_blocks_head = NULL;
	    loop->_blocks_tail = NULL;
	    loop->_counterpart = NULL;
	    loop->_pthread = t;
	#if DEPLOYMENT_TARGET_WINDOWS
	    loop->_winthread = GetCurrentThreadId();
	#else
	    loop->_winthread = 0;
	#endif
	    rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true);
	    if (NULL != rlm) __CFRunLoopModeUnlock(rlm);
	    return loop;
	}
  • 进入 CFRunLoopRef 的定义,根据定义得知,其实 RunLoop 也是一个对象,是 __CFRunLoop 结构体的指针类型:
	typedef struct __CFRunLoop * CFRunLoopRef;
	struct __CFRunLoop {
	    CFRuntimeBase _base;
	    pthread_mutex_t _lock;            /* locked for accessing mode list */
	    __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
	    Boolean _unused;
	    volatile _per_run_data *_perRunData;              // reset for runs of the run loop
	    pthread_t _pthread;
	    uint32_t _winthread;
	    CFMutableSetRef _commonModes;
	    CFMutableSetRef _commonModeItems;
	    CFRunLoopModeRef _currentMode;
	    CFMutableSetRef _modes;
	    struct _block_item *_blocks_head;
	    struct _block_item *_blocks_tail;
	    CFTypeRef _counterpart;
	};
  • 分析 RunLoop 的底层定义可以得出,一个 RunLoop 依赖于多个 Mode,意味着一个 RunLoop 需要处理多个事务,即一个 Mode 对应多个Item,而一个 item 中,包含了 timer、source、observer,如下所示:

在这里插入图片描述

② Mode 类型
  • Mode 在苹果文档中涉及的有五个,而在 iOS 中公开出来的只有 NSDefaultRunLoopMode 和 NSRunLoopCommonModes。
  • NSRunLoopCommonModes 实际上是一个 Mode 的集合,默认包括 NSDefaultRunLoopMode 和 NSEventTrackingRunLoopMode。
  • Mode 类型如下:
    • NSDefaultRunLoopMode:默认的 mode,用的最多的模式,大多数情况应该使用该模式开始 Runloop 并配置 input source。
    • NSConnectionReplyMode:cocoa 用这个模式结合 NSConnection 对象监测回放,我们开发中应该很少使用这种模式。
    • NSModalPanelRunLoopMode:cocoa 用这个模式来标识用于模态面板的事件。
    • NSEventTrackingRunLoopMode:使用该 Mode 去跟踪来自用户交互的事件(比如UITableView上下滑动)。
    • NSRunLoopCommonModes:一组可配置的常用模式,占位模式不是一个真正的模式,是一个伪模式,灵活性更好。
③ Source & Timer & Observer
  • Source 表示可以唤醒 RunLoop 的一些事件,例如用户点击了屏幕,就会创建一个 RunLoop,主要分为 Source0 和 Source1;
    • Source0 表示非系统事件,即用户自定义的事件;
    • Source1 表示系统事件,主要负责底层的通讯,具备唤醒能力。
  • Timer 就是常用的 NSTimer 定时器一类;
  • Observer 主要用于监听 RunLoop 的状态变化,并作出一定响应,主要有以下一些状态:
	typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
	    // 进入RunLoop
	    kCFRunLoopEntry = (1UL << 0),
	    // 即将处理Timers
	    kCFRunLoopBeforeTimers = (1UL << 1),
	    // 即将处理Source
	    kCFRunLoopBeforeSources = (1UL << 2),
	    // 即将进入休眠
	    kCFRunLoopBeforeWaiting = (1UL << 5),
	    // 被唤醒
	    kCFRunLoopAfterWaiting = (1UL << 6),
	    // 退出RunLoop
	    kCFRunLoopExit = (1UL << 7),
	    kCFRunLoopAllActivities = 0x0FFFFFFFU
	};
  • 我们通过 lldb 命令获取 mainRunloop、currentRunloop 的 currentMode,可以得出:Runloop 在运行时的 mode 只存在一个。如下:
	CFRunLoopRef mainRunloop = CFRunLoopGetMain();
    CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();

	// 调试
	(lldb) po CFRunLoopCopyCurrentMode(mainRunloop)
	kCFRunLoopDefaultMode
	
	(lldb) po CFRunLoopCopyCurrentMode(currentRunloop)
	kCFRunLoopDefaultMode
  • 获取 mainRunloop 的所有模型,即 po CFRunLoopCopyAllModes(mainRunloop):可以得出 Runloop 和 CFRunloopMode 具有 一对多的关系。如下:
	(lldb) po CFRunLoopCopyAllModes(mainRunloop)
	<__NSArrayM 0x6000038e3ea0>(
	UITrackingRunLoopMode,
	GSEventReceiveRunLoopMode,
	kCFRunLoopDefaultMode
	)
  • 通过 bt 查看堆栈信息,可以看出 timer 的 item 类型如下所示:

在这里插入图片描述

  • 在 RunLoop 源码中也可以查看 Item 类型:
    • block应用:CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
    • 调用timer:CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
    • 响应 source0: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
    • 响应 source1: CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
    • GCD 主队列:CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
    • observer 源: CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
  • 以 Timer 为例,初始化 timer 时,都会将 timer 通过addTimer:forMode:方法添加到 Runloop 中,我们在源码中查找 addTimer 的相关方法,即 CFRunLoopAddTimer 方法,其实现主要判断是否是 kCFRunLoopCommonModes,然后查找 runloop 的 mode 进行匹配处理:
    • 其中 kCFRunLoopCommonModes 不是一种模式,是一种抽象的伪模式,比 defaultMode 更加灵活;
    • 通过 CFSetAddValue(rl->_commonModeItems, rlt);可以得知,runloop 与 mode 是一对多的,同时可以得出 mode 与 item 也是一对多的;
	void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
	    CHECK_FOR_FORK();
	    if (__CFRunLoopIsDeallocating(rl)) return;
	    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
	    __CFRunLoopLock(rl);
	    
	    // 重点 : kCFRunLoopCommonModes
	    if (modeName == kCFRunLoopCommonModes) {
	        // 如果是 kCFRunLoopCommonModes 类型
	        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	        
	        if (NULL == rl->_commonModeItems) {
	            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	        }
	        // runloop与mode 是一对多的, mode与item也是一对多的
	        CFSetAddValue(rl->_commonModeItems, rlt);
	        if (NULL != set) {
	            CFTypeRef context[2] = {rl, rlt};
	            /* add new item to all common-modes */
	            // 执行
	            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
	            CFRelease(set);
	        }
	    } else {
	        // 如果是非commonMode类型
	        // 查找runloop的模型
	        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
	        if (NULL != rlm) {
	            if (NULL == rlm->_timers) {
	                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
	                cb.equal = NULL;
	                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
	            }
	        }
	        // 判断mode是否匹配
	        if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
	            __CFRunLoopTimerLock(rlt);
	            if (NULL == rlt->_runLoop) {
	                rlt->_runLoop = rl;
	            } else if (rl != rlt->_runLoop) {
	                __CFRunLoopTimerUnlock(rlt);
	                __CFRunLoopModeUnlock(rlm);
	                __CFRunLoopUnlock(rl);
	                return;
	            }
	            // 如果匹配,则将runloop加进去,而runloop的执行依赖于  [runloop run]
	            CFSetAddValue(rlt->_rlModes, rlm->_name);
	            __CFRunLoopTimerUnlock(rlt);
	            __CFRunLoopTimerFireTSRLock();
	            __CFRepositionTimerInMode(rlm, rlt, false);
	            __CFRunLoopTimerFireTSRUnlock();
	            if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) {
	                // Normally we don't do this on behalf of clients, but for
	                // backwards compatibility due to the change in timer handling...
	                if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl);
	            }
	        }
	        if (NULL != rlm) {
	            __CFRunLoopModeUnlock(rlm);
	        }
	    }
	   
	    __CFRunLoopUnlock(rl);
	}void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
	    CHECK_FOR_FORK();
	    if (__CFRunLoopIsDeallocating(rl)) return;
	    if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
	    __CFRunLoopLock(rl);
	    
	    // 重点 : kCFRunLoopCommonModes
	    if (modeName == kCFRunLoopCommonModes) {
	        // 如果是 kCFRunLoopCommonModes 类型
	        CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
	        
	        if (NULL == rl->_commonModeItems) {
	            rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
	        }
	        // runloop与mode 是一对多的, mode与item也是一对多的
	        CFSetAddValue(rl->_commonModeItems, rlt);
	        if (NULL != set) {
	            CFTypeRef context[2] = {rl, rlt};
	            /* add new item to all common-modes */
	            //执行
	            CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
	            CFRelease(set);
	        }
	    } else {
	        // 如果是非commonMode类型
	        // 查找runloop的模型
	        CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
	        if (NULL != rlm) {
	            if (NULL == rlm->_timers) {
	                CFArrayCallBacks cb = kCFTypeArrayCallBacks;
	                cb.equal = NULL;
	                rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb);
	            }
	        }
	        // 判断mode是否匹配
	        if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) {
	            __CFRunLoopTimerLock(rlt);
	            if (NULL == rlt->_runLoop) {以上是关于iOS之深入解析Runloop的底层原理的主要内容,如果未能解决你的问题,请参考以下文章

iOS之深入解析类方法+load与+initialize的底层原理

iOS之深入解析YYModel的底层原理

iOS-深入理解(转载)

深入理解RunLoop

深入理解RunLoop(转载)

李洪强iOS开发之RunLoop的原理和核心机制