一、RunLoop简介
1.RunLoop介绍
一个线程一次只能实行一个任务,实行完毕后就会退出。如果需要一个机制,让线程能随时处理事件,处理完毕后并不退出,代码逻辑是这样的:
- do {
- //获取消息
- //处理消息
- } while (消息 != 退出)
复制代码 这种模型通常被称为Event Loop,Event loop 在很多体系和框架都有实现。比方:Node.js 的事件处理,Windows 程序的消息循环,而在macOS、iOS中就是Run Loop。
简朴的说RunLoop实在就是一种循环,通过这种循环得以让应用程序持续运行,让线程能随时处理事件,并在线程需要举行事件处理时忙起来,在不需要举行事件处理时闲下来。
2.RunLoop功能
- 保持程序的持续运行
- 处理app中各种事件
- 节省CPU资源,进步程序性能:该办事时办事,该休眠时休眠。而且休眠时不占用CPU
3.RunLoop使用场景
只有在创建辅助线程时,才需要显式启动RunLoop。app 启动过程中主线程会主动启动RunLoop。即使用 Xcode 提供的模版创建 app,无需为主线程显式启动RunLoop。
RunLoop用于需要与线程交互的情况。对于辅助线程,根据需要配置、启动 RunLoop,但并非所有线程都需要使用 RunLoop。比方,实行一些长时间运行的固定任务,则无需启动RunLoop。如有以下需求,则需要启动RunLoop:
- 使用端口或自定义 input source 与其他线程通讯。
- 在线程上使用计时器。
- 使用任一 performSelector 方法。
- 线程需要定期实行任务。
RunLoop 的配置和启动也很简朴,但需要在某些条件下退出 RunLoop。最好清理所有工作退却出RunLoop,而非欺凌终止线程。
4.Run Loop 与线程
下图为RunLoop与线程的关系:
Runloop 在线程中的作用主要是从 input source 和 timer source 接受事件,然后在线程中处理事件。
输入源(Input source):异步传递事件,通常是从其他线程、app 发送的消息。
计时器源(Timer source):同步传递事件,这些事件在计划的时间、间隔触发。
Input source 传递异步事件到对应处理程序,并退出runUntilDate:方法。Timer source 传递事件到对应处理程序,但不会退出 run loop。
runUntilDate:方法在指定时间到达前会保持RunLoop 处于运行状态,RunLoop运行时会处理 input source 的各种事件。
Runloop 和线程是绑定在一起的。每个线程(包罗主线程)都有一个对应的 Runloop 对象。我们并不能自己创建 Runloop 对象,但是可以获取到体系提供的 Runloop 对象。
主线程的 Runloop 会在应用启动的时间完成启动,其他线程的 Runloop 默认并不会启动,需要我们手动启动。
除处理 input source 传递的任务,RunLoop还会发送其当前状态的关照。注册观察RunLoop状态变化,可以在状态变化时举行额外工作。使用 Core Foundation 的CFRunLoopObserverCreate()或CFRunLoopObserverCreateWithHandler()方法创建观察者,CFRunLoopAddObserver()方法添加观察者,最后使用CFRelease()开释observer。
5.RunLoop源代码和模型图
- struct __CFRunLoop {
- CFRuntimeBase _base;
- pthread_mutex_t _lock; /* locked for accessing mode list */
- __CFPort _wakeUpPort; // used for CFRunLoopWakeUp 内核向该端口发送消息可以唤醒runloop
- Boolean _unused;
- volatile _per_run_data *_perRunData; // reset for runs of the run loop
- pthread_t _pthread; //RunLoop对应的线程
- uint32_t _winthread;
- CFMutableSetRef _commonModes; //存储的是字符串,记录所有标记为common的mode
- CFMutableSetRef _commonModeItems;//存储所有commonMode的item(source、timer、observer)
- CFRunLoopModeRef _currentMode; //当前运行的mode
- CFMutableSetRef _modes; //存储的是CFRunLoopModeRef
- struct _block_item *_blocks_head;//doblocks的时候用到
- struct _block_item *_blocks_tail;
- CFTypeRef _counterpart;
- };
复制代码 通过代码不难发现RunLoop实在也是个对象,一个RunLoop对象,主要包罗了一个线程,若干个Mode,若干个commonMode,若干个commonModeItems还有一个当前运行的Mode(currentMode)。
二、RunLoop Mode
1.CFRunLoopModeRef
RunLoop的Mode属于CFRunLoopModeRef类,其代码结构如下:
- typedef struct __CFRunLoopMode *CFRunLoopModeRef;
- struct __CFRunLoopMode {
- CFRuntimeBase _base;
- pthread_mutex_t _lock; /* must have the run loop locked before locking this */
- CFStringRef _name; //mode名称,运行模式是通过名称来识别的
- Boolean _stopped; //mode是否被终止
- char _padding[3];
- //整个结构体最核心的部分
- ------------------------------------------
- CFMutableSetRef _sources0; // Sources0
- CFMutableSetRef _sources1; // Sources1
- CFMutableArrayRef _observers; // 观察者
- CFMutableArrayRef _timers; // 定时器
- ------------------------------------------
- CFMutableDictionaryRef _portToV1SourceMap;//字典 key是mach_port_t,value是CFRunLoopSourceRef
- __CFPortSet _portSet;//保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中
- CFIndex _observerMask;
- #if USE_DISPATCH_SOURCE_FOR_TIMERS
- dispatch_source_t _timerSource;
- dispatch_queue_t _queue;
- Boolean _timerFired; // set to true by the source when a timer has fired
- Boolean _dispatchTimerArmed;
- #endif
- #if USE_MK_TIMER_TOO
- mach_port_t _timerPort;
- Boolean _mkTimerArmed;
- #endif
- #if DEPLOYMENT_TARGET_WINDOWS
- DWORD _msgQMask;
- void (*_msgPump)(void);
- #endif
- uint64_t _timerSoftDeadline; /* TSR */
- uint64_t _timerHardDeadline; /* TSR */
- };
复制代码
- 通过源代码可以看出一个CFRunLoopModeRef对象有一个name,若干source0,source1,timer,observer和port,可以看出来事件都是由mode在管理,而RunLoop管理着Mode。
- Runloop Mode 实际上是 Source,Timer 和 Observer 的集合,不同的 Mode 把不同组的
Source,Timer 和 Observer 隔绝开来。Runloop 在某个时间只能跑在一个 Mode 下(这个 mode 被称作 currentMode),处理这一个 Mode当中的 Source,Timer 和 Observer。如果需要切换 mode,需退出 loop,重新进入要切换的 mode。这样做可以隔离不同组 source、timer、observer,让其互不影响。
正如下图所示:
Mode 可用于过滤不需要的 source。大部分情况下,使用体系定义的 default mode 即可。UIScrollView滑动时主线程会进入UITrackingRunLoopMode。此时,只有与UITrackingRunLoopMode关联的 mode 可以向主线程传递事件。对于辅助线程,可以使用自定义 mode 来防止低优先级 source 在时间紧迫的操纵期间传递事件。
2.RunLoop Mode的五种模式
- NSDefaultRunLoopMode:App的默认Mode,通常主线程是在这个Mode下运行
- NSConnectionReplyMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- NSModalPanelRunLoopMode:当应用程序表现一个模态对话框时使用此模式。在此模式下,RunLoop只处理与模态面板相关的事件,忽略其他非模态事件,确保用户只能与模态面板交互。
- NSEventTrackingRunLoopMode:接受体系事件的内部 Mode,通常用不到
- NSRunLoopCommonModes:这不是一个单一的模式,而是一个特殊的集合,包罗了多个模式,其中最典范的就是默认模式(NSDefaultRunLoopMode)和事件跟踪模式(NSEventTrackingRunLoopMode)。将输入源(如定时器)添加到NSRunLoopCommonModes,意味着它会在所有包罗的模式下有效,即使RunLoop切换模式也不会停止工作。
CommonModes:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的
“commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会主动将 _commonModeItems 里的
Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。
3.RunLoop Mode使用
CFRunLoop对外袒露的管理 Mode 接口只有下面2个:
- CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
- CFRunLoopRunInMode(CFStringRef modeName, ...);
复制代码 Mode 袒露的管理 mode item 的接口有下面几个:
- 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);
复制代码
- 只能通过 mode name 来操纵内部的 mode,当你传入一个新的 mode name 但 RunLoop 内部没有对应 mode
时,RunLoop会主动帮你创建对应的 CFRunLoopModeRef。
- 对于一个 RunLoop 来说,其内部的 mode 只能增加不能删除。
官方公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用这两个 Mode Name 来操纵其对应的 Mode。
公开提供的 Mode 有两个:kCFRunLoopDefaultMode (NSDefaultRunLoopMode) 和 UITrackingRunLoopMode,你可以用这两个 Mode Name 来操纵其对应的 Mode。
同时官方还提供了一个操纵 Common 标记的字符串:kCFRunLoopCommonModes (NSRunLoopCommonModes),你可以用这个字符串来操纵 Common Items,或标记一个 Mode 为 “Common”。使用时注意区分这个字符串和其他 mode name。
三、RunLoop Source
Run Loop Source分为Source、Observer、Timer三种,他们统称为ModeItem。
1.CFRunLoopSourceRef
根据官方描述,CFRunLoopSourceRef是input sources的抽象。
CFRunLoopSource分为Source0和Source1两个版本。
它的结构如下:
- struct __CFRunLoopSource {
- CFRuntimeBase _base;
- uint32_t _bits; //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被处理
- pthread_mutex_t _lock;
- CFIndex _order; /* immutable */
- CFMutableBagRef _runLoops;
- union {
- CFRunLoopSourceContext version0; /* immutable, except invalidation */
- CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
- } _context;
- };
复制代码 sourc0:
是App内部事件,由App自己管理的UIEvent、CFSocket都是source0。当一个source0事件准备实行的时间,必须要先把它标记为signal状态,以下是source0的结构体:
- typedef struct {
- CFIndex version;
- void * info;
- const void *(*retain)(const void *info);
- void (*release)(const void *info);
- CFStringRef (*copyDescription)(const void *info);
- Boolean (*equal)(const void *info1, const void *info2);
- CFHashCode (*hash)(const void *info);
- void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
- void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
- void (*perform)(void *info);
- } CFRunLoopSourceContext;
复制代码 source0是非基于Port的。只包罗了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。实用于那些不需要底层硬件或体系事件驱动的场景,比如基于条件的任务,当满足某个条件时手动触发。
source1:
source1由RunLoop和内核管理,source1带有mach_port_t,可以接收内核消息并触发回调,以下是source1的结构体:
- typedef struct {
- CFIndex version;
- void * info;
- const void *(*retain)(const void *info);
- void (*release)(const void *info);
- CFStringRef (*copyDescription)(const void *info);
- Boolean (*equal)(const void *info1, const void *info2);
- CFHashCode (*hash)(const void *info);
- #if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
- mach_port_t (*getPort)(void *info);
- void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
- #else
- void * (*getPort)(void *info);
- void (*perform)(void *info);
- #endif
- } CFRunLoopSourceContext1;
复制代码 Source1包罗了mach_port和一个回调(函数指针),Source1可以监听体系端口,通过内核和其他线程通信,接收、分发体系事件,他能主动唤醒RunLoop(由操纵体系内核举行管理)。而且Source1在处理的时间会分发一些操纵给Source0行止理。广泛应用于处理体系级事件,如触摸事件、摇摆、锁屏等,以及历程间通信(IPC)。由于它直接与体系内核和其他历程或线程的事件交互,因此可以实现高效的事件传递和响应。
2.CFRunLoopTimerRef
CFRunLoopTimer是基于时间的触发器,其包罗一个时间长度、一个回调(函数指针)。当其到场runloop时,runloop会注册对应的时间点,其时间点到时,runloop会被唤醒以实行那个回调。
而且CFRunLoopTimer和NSTimer是toll-free bridged(对象桥接),可以相互转换。其结构如下:
- struct __CFRunLoopTimer {
- CFRuntimeBase _base;
- uint16_t _bits; //标记fire状态
- pthread_mutex_t _lock;
- CFRunLoopRef _runLoop; //添加该timer的runloop
- CFMutableSetRef _rlModes; //存放所有 包含该timer的 mode的 modeName,意味着一个timer可能会在多个mode中存在
- CFAbsoluteTime _nextFireDate;
- CFTimeInterval _interval; //理想时间间隔 /* immutable */
- CFTimeInterval _tolerance; //时间偏差 /* mutable */
- uint64_t _fireTSR; /* TSR units */
- CFIndex _order; /* immutable */
- CFRunLoopTimerCallBack _callout; /* immutable */
- CFRunLoopTimerContext _context; /* immutable, except invalidation */
- };
复制代码 Timer source 在预设时间将事件同步传递到线程。Timer 是线程关照自己实行任务的一种方式。比方,用户在搜刮框键入字符时,使用计时器间隔指定时间搜刮一次,以便用户输入更多笔墨,也可以进步性能。
只管 Timer 会产生基于时间的关照,但它不是实时的机制。与 input source 一样,timer 也与RunLoop Mode 关联。如果计时器触发时,RunLoop正在实行其他回调,计时器会等待 RunLoop实行其回调;如果RunLoop未运行,timer 永不触发。
计时器可以配置为一次性或重复生成事件。重复计时器会根据计划的触发时间,而非实际触发时间,主动重新计划自身。比方,计时器计划在指定时间触发,并每5秒触发一次,则触发时间永远会落在原始时间的5秒间隔上。如果触发时间被延误很多,导致缺少一个或多个触发时间,计时器将在错过的时间段内仅触发一次。此后,计时器将重新计划下一个计划的触发时间。
对于NSTimer,当我们用scheduledTimerWithTimeInterval方法初始化定时器时
- [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
复制代码 体系会将NSTimer主动到场NSDefaultRunLoopMode模式中,所以所以它就等同于下面代码:
- NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
- [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
复制代码 3.CFRunLoopObserverRef
CFRunLoopObserverRef是观察者可以观察Runloop的各种状态,每个Observer都包罗了一个回调(函数指针),当RunLoop的状态发生变化时,观察者就能通过回调接收到这个变化。
- typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
- struct __CFRunLoopObserver {
- CFRuntimeBase _base;
- pthread_mutex_t _lock;
- CFRunLoopRef _runLoop;//监听的RunLoop
- CFIndex _rlCount;//添加该Observer的RunLoop对象个数
- CFOptionFlags _activities; /* immutable */
- CFIndex _order;//同时间最多只能监听一个
- CFRunLoopObserverCallBack _callout;//监听的回调
- CFRunLoopObserverContext _context;//上下文用于内存管理
- };
复制代码 Run loop 可观察时间点有以下几个:
- /* Run Loop Observer Activities */
- typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
- kCFRunLoopEntry = (1UL << 0), // 即将进入 loop
- kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 Timer
- kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
- kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
- kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒,但还未执行将其唤醒的任务。
- kCFRunLoopExit = (1UL << 7), // 即将退出 loop
- kCFRunLoopAllActivities = 0x0FFFFFFFU // 观察所有状态变化
- };
复制代码 Runloop 通过监控 Source 来决定有没有任务要做,除此之外,我们还可以用 Runloop Observer 来监控 Runloop 本身的状态。 Runloop Observer 可以监控上面的 Runloop 事件,具体流程如下图:
这六种状态都可以被observer观察到,我们也可以利用这一方法写一些特殊事件,创建监听,监听RunLoop的状态变化:
- // 创建 observer
- CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
- switch (activity) {
- case kCFRunLoopEntry:
- NSLog(@"kCFRunLoopEntry -- %@", CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent()));
- break;
- case kCFRunLoopBeforeTimers:
- NSLog(@"kCFRunLoopBeforeTimers");
- break;
- case kCFRunLoopBeforeSources:
- NSLog(@"kCFRunLoopBeforeSources");
- break;
- case kCFRunLoopBeforeWaiting:
- NSLog(@"kCFRunLoopBeforeWaiting");
- break;
- case kCFRunLoopAfterWaiting:
- NSLog(@"kCFRunLoopAfterWaiting");
- break;
- case kCFRunLoopExit:
- NSLog(@"kCFRunLoopExit");
- break;
- default:
- NSLog(@"default");
- break;
- }
- });
- // 将 observer 添加到主线程的 run loop 的 common modes
- CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
- // 释放 observer
- CFRelease(observer);
复制代码
四、RunLoop实现
1.获取RunLoop对象
需要知道的是RunLoop对象不能手动创建,前面提到每个线程都有一个RunLoop对象所以只能通过线程来获取对应的RunLoop对象。
Runloop对象主要有两种获取方式:
- // Foundation
- NSRunLoop *runloop = [NSRunLoop currentRunLoop]; // 获得当前RunLoop对象
- NSRunLoop *runloop = [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
复制代码 NSRunloop类是Fundation框架中Runloop的对象,而且NSRunLoop是基于CFRunLoopRef的封装,提供了面向对象的API,但是这些API不是线程安全的。
- // Core Foundation
- CFRunLoopRef runloop = CFRunLoopGetCurrent(); // 获得当前RunLoop对象
- CFRunLoopRef runloop = CFRunLoopGetMain(); // 获得主线程的RunLoop对象
复制代码 CFRunLoopRef类是CoreFoundation框架中Runloop的对象,而且其提供了纯C语言函数的API,所有这些API都是线程安全。
CoreFoundation框架中这两个函数的具体实现:
- CFRunLoopRef CFRunLoopGetCurrent(void) {
- CHECK_FOR_FORK();
- CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
- if (rl) return rl;
- return _CFRunLoopGet0(pthread_self());
- }
- CFRunLoopRef CFRunLoopGetMain(void) {
- CHECK_FOR_FORK();
- static CFRunLoopRef __main = NULL; // no retain needed
- if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
- return __main;
- }
复制代码 这两个方法里都是调用了_CFRunLoopGet0这个方法实现传入线程的函数,下面看下CFRunLoopGet0的结构:
- static CFMutableDictionaryRef __CFRunLoops = NULL;
- static CFSpinLock_t loopsLock = CFSpinLockInit;
-
- // t==0 is a synonym for "main thread" that always works
- //根据线程取RunLoop
- CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
- if (pthread_equal(t, kNilPthreadT)) {
- t = pthread_main_thread_np();
- }
- __CFSpinLock(&loopsLock);
- //如果存储RunLoop的字典不存在
- if (!__CFRunLoops) {
- __CFSpinUnlock(&loopsLock);
- //创建一个临时字典dict
- CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
- //创建主线程的RunLoop
- CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
- //把主线程的RunLoop保存到dict中,key是线程,value是RunLoop
- CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
- //此处NULL和__CFRunLoops指针都指向NULL,匹配,所以将dict写到__CFRunLoops
- if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
- //释放dict
- CFRelease(dict);
- }
- //释放mainrunloop
- CFRelease(mainLoop);
- __CFSpinLock(&loopsLock);
- }
- //以上说明,第一次进来的时候,不管是getMainRunloop还是get子线程的runloop,主线程的runloop总是会被创建
- //从字典__CFRunLoops中获取传入线程t的runloop
- CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
- __CFSpinUnlock(&loopsLock);
- //如果没有获取到
- if (!loop) {
- //根据线程t创建一个runloop
- CFRunLoopRef newLoop = __CFRunLoopCreate(t);
- __CFSpinLock(&loopsLock);
- loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
- if (!loop) {
- //把newLoop存入字典__CFRunLoops,key是线程t
- 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)) {
- //注册一个回调,当线程销毁时,销毁对应的RunLoop
- _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
- }
- }
- return loop;
- }
复制代码 这段代码可以得出以下结论:
- RunLoop和线程的逐一对应的,对应的方式是以key-value的方式生存在一个全局字典中
- 主线程的RunLoop会在初始化全局字典时创建
- 子线程的RunLoop会在第一次获取的时间创建,如果不获取的话就一直不会被创建
- RunLoop会在线程销毁时销毁
2.添加Mode
对于Mode苹果官方提供了以下三个接口:
- //向当前RunLoop的common modes中添加一个mode。
- CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode)
- //返回当前运行的mode的name
- CFRunLoopCopyCurrentMode(CFRunLoopRef rl)
- //返回当前RunLoop的所有mode
- CFRunLoopCopyAllModes(CFRunLoopRef rl)
复制代码 我们没有办法直接创建一个CFRunLoopMode对象,但是我们可以调用CFRunLoopAddCommonMode方法传入一个字符串向RunLoop中添加Mode,传入的字符串即为Mode的名字,Mode对象应该是此时在RunLoop内部创建的。
下面是CFRunLoopAddCommonMode源码:
- void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
- CHECK_FOR_FORK();
- if (__CFRunLoopIsDeallocating(rl)) return;
- __CFRunLoopLock(rl);
- //看rl中是否已经有这个mode,如果有就什么都不做
- if (!CFSetContainsValue(rl->_commonModes, modeName)) {
- CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
- //把modeName添加到RunLoop的_commonModes中
- CFSetAddValue(rl->_commonModes, modeName);
- if (NULL != set) {
- CFTypeRef context[2] = {rl, modeName};
- /* add all common-modes items to new mode */
- //这里调用CFRunLoopAddSource/CFRunLoopAddObserver/CFRunLoopAddTimer的时候会调用
- //__CFRunLoopFindMode(rl, modeName, true),CFRunLoopMode对象在这个时候被创建
- CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
- CFRelease(set);
- }
- } else {
- }
- __CFRunLoopUnlock(rl);
- }
复制代码
- modeName不能重复,modeName是mode的唯一标识符
- RunLoop的_commonModes数组存放所有被标记为common的mode的名称(name)
- 添加commonMode会把commonModeItems数组中的所有source同步到新添加的mode中
- CFRunLoopMode对象在CFRunLoopAddItemsToCommonMode函数中调用CFRunLoopFindMode时被创建
3.添加Run Loop Source(ModeItem)
在RunLoop Mode部分已经介绍过管理RunLoop Source的几个接口方法,这里就不再赘述。
CFRunLoopAddSource的代码结构如下:
- //添加source事件
- void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
- CHECK_FOR_FORK();
- if (__CFRunLoopIsDeallocating(rl)) return;
- if (!__CFIsValid(rls)) return;
- Boolean doVer0Callout = false;
- __CFRunLoopLock(rl);
- //如果是kCFRunLoopCommonModes
- if (modeName == kCFRunLoopCommonModes) {
- //如果runloop的_commonModes存在,则copy一个新的复制给set
- CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
- //如果runl _commonModeItems为空
- if (NULL == rl->_commonModeItems) {
- //先初始化
- rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
- }
- //把传入的CFRunLoopSourceRef加入_commonModeItems
- CFSetAddValue(rl->_commonModeItems, rls);
- //如果刚才set copy到的数组里有数据
- if (NULL != set) {
- CFTypeRef context[2] = {rl, rls};
- /* add new item to all common-modes */
- //则把set里的所有mode都执行一遍__CFRunLoopAddItemToCommonModes函数
- CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
- CFRelease(set);
- }
- //以上分支的逻辑就是,如果你往kCFRunLoopCommonModes里面添加一个source,那么所有_commonModes里的mode都会添加这个source
- } else {
- //根据modeName查找mode
- CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
- //如果_sources0不存在,则初始化_sources0,_sources0和_portToV1SourceMap
- if (NULL != rlm && NULL == rlm->_sources0) {
- rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
- rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
- rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
- }
- //如果_sources0和_sources1中都不包含传入的source
- if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
- //如果version是0,则加到_sources0
- if (0 == rls->_context.version0.version) {
- CFSetAddValue(rlm->_sources0, rls);
- //如果version是1,则加到_sources1
- } else if (1 == rls->_context.version0.version) {
- CFSetAddValue(rlm->_sources1, rls);
- __CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
- if (CFPORT_NULL != src_port) {
- //此处只有在加到source1的时候才会把souce和一个mach_port_t对应起来
- //可以理解为,source1可以通过内核向其端口发送消息来主动唤醒runloop
- CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
- __CFPortSetInsert(src_port, rlm->_portSet);
- }
- }
- __CFRunLoopSourceLock(rls);
- //把runloop加入到source的_runLoops中
- if (NULL == rls->_runLoops) {
- rls->_runLoops = CFBagCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeBagCallBacks); // sources retain run loops!
- }
- CFBagAddValue(rls->_runLoops, rl);
- __CFRunLoopSourceUnlock(rls);
- if (0 == rls->_context.version0.version) {
- if (NULL != rls->_context.version0.schedule) {
- doVer0Callout = true;
- }
- }
- }
- if (NULL != rlm) {
- __CFRunLoopModeUnlock(rlm);
- }
- }
- __CFRunLoopUnlock(rl);
- if (doVer0Callout) {
- // although it looses some protection for the source, we have no choice but
- // to do this after unlocking the run loop and mode locks, to avoid deadlocks
- // where the source wants to take a lock which is already held in another
- // thread which is itself waiting for a run loop/mode lock
- rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
- }
- }
复制代码 通过添加source的这段代码可以得出如下结论:
- 如果modeName传入kCFRunLoopCommonModes,则该source会被生存到RunLoop的_commonModeItems中
- 如果modeName传入kCFRunLoopCommonModes,则该source会被添加到所有commonMode中
- 如果modeName传入的不是kCFRunLoopCommonModes,则会先查找该Mode,如果没有,会创建一个
同一个source在一个mode中只能被添加一次
五、RunLoop实现逻辑
- //用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);
- //调用函数__CFRunLoopRun 进入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)回调,处理Source0
- 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;
- }
- //6.通知Observers:RunLoop的线程即将进入休眠
- 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);
- //设置do-while之后的返回值
- 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);
- }
复制代码 实际上RunLoop就是这样的一个函数,其内部是一个do-while循环。当你调用CFRunLoopRun()时,线程就会一直停留在这个循环里,知道超时或者被手动调用,该函数才会返回。
RunLoop回调(流程)
当App启动时,体系会默认注册五个上面说过的5个mode
当RunLoop举行回调时,一般都是通过一个很长的函数调出去(call out),当在代码中加断点调试时,通常能在调用栈上看到这些函数。这就是RunLoop的流程:
- {
- /// 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);
- }
复制代码 RunLoop启动方法
1.run,无条件
无条件地进入运行循环是最简朴的选项,但也是最不抱负的选择。无条件地运行runloop将线程放入永久循环,这使您无法控制运行循环本身。停止runloop的唯一方法是杀死它。也没有办法在自定义模式下运行循环。
2.runUntilDate, 设置时间限定
设置了超时时间,超过这个时间runloop结束,优于第一种
3.runMode:beforeDate:,在特定模式下
相对比较好的方式,可以指定runloop以哪种模式运行,但是它是单次调用的,超时时间到达或者一个输入源被处理,则runLoop就会主动退出,上述两种方式都是循环调用的
实际上run方法的实现就是无限调用runMode:beforeDate:方法
runUntilDate:也会重复调用runMode:beforeDate:方法,区别在于它超时就不会再调用
RunLoop关闭方法
1.将运行循环配置为使用超时值运行。
2.手动停止。
这里需要注意,虽然删除runloop的输入源和定时器大概会导致运行循环的退出,但这并不是个可靠的方法,体系大概会添加输入源到runloop中,但在我们的代码中大概并不知道这些输入源,因此无法删除它们,导致无法退出runloop。
我们可以通过上述2、3方法来启动runloop,设置超时时间。但是如果需要对这个线程和它的RunLoop有最准确的控制,而并不是依赖超时机制,这时我们可以通过 CFRunLoopStop()方法来手动结束一个 RunLoop。但是 CFRunLoopStop()方法只会结束当前正在实行的这次runMode:beforeDate:调用,而不会结束后续runloop的调用。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。 |