文章目录
- 一、RunLoop简介
- 1.RunLoop介绍
- 2.RunLoop功能
- 3.RunLoop使用场景
- 4.Run Loop 与线程
- 5.RunLoop源代码和模型图
- 二、RunLoop Mode
- 1.CFRunLoopModeRef
- 2.RunLoop Mode的五种模式
- 3.RunLoop Mode使用
- 三、RunLoop Source
- 1.CFRunLoopSourceRef
- sourc0:
- source1:
- 2.CFRunLoopTimerRef
- 3.CFRunLoopObserverRef
- 四、RunLoop实现
- 1.获取RunLoop对象
- 2.添加Mode
- 3.添加Run Loop Source(ModeItem)
- 五、RunLoop实现逻辑
一、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 内核向该端口发送消息可以唤醒runloopBoolean _unused;volatile _per_run_data *_perRunData; // reset for runs of the run looppthread_t _pthread; //RunLoop对应的线程uint32_t _winthread;CFMutableSetRef _commonModes; //存储的是字符串,记录所有标记为common的modeCFMutableSetRef _commonModeItems;//存储所有commonMode的item(source、timer、observer)CFRunLoopModeRef _currentMode; //当前运行的modeCFMutableSetRef _modes; //存储的是CFRunLoopModeRefstruct _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; // Sources0CFMutableSetRef _sources1; // Sources1CFMutableArrayRef _observers; // 观察者CFMutableArrayRef _timers; // 定时器
------------------------------------------CFMutableDictionaryRef _portToV1SourceMap;//字典 key是mach_port_t,value是CFRunLoopSourceRef__CFPortSet _portSet;//保存所有需要监听的port,比如_wakeUpPort,_timerPort都保存在这个数组中CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERSdispatch_source_t _timerSource;dispatch_queue_t _queue;Boolean _timerFired; // set to true by the source when a timer has firedBoolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOOmach_port_t _timerPort;Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWSDWORD _msgQMask;void (*_msgPump)(void);
#endifuint64_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);
#elsevoid * (*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的runloopCFMutableSetRef _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;//监听的RunLoopCFIndex _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), // 即将进入 loopkCFRunLoopBeforeTimers = (1UL << 1), // 即将处理 TimerkCFRunLoopBeforeSources = (1UL << 2), // 即将处理 SourcekCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中唤醒,但还未执行将其唤醒的任务。kCFRunLoopExit = (1UL << 7), // 即将退出 loopkCFRunLoopAllActivities = 0x0FFFFFFFU // 观察所有状态变化
};
Runloop 通过监控 Source 来决定有没有任务要做,除此之外,我们还可以用 Runloop Observer 来监控 Runloop 本身的状态。 Runloop Observer 可以监控上面的 Runloop 事件,具体流程如下图:
这六种状态都可以被observer观察到,我们也可以利用这一方法写一些特殊事件,创建监听,监听RunLoop的状态变化:
// 创建 observerCFRunLoopObserverRef 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 modesCFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);// 释放 observerCFRelease(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 neededif (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS neededreturn __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);//创建一个临时字典dictCFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);//创建主线程的RunLoopCFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());//把主线程的RunLoop保存到dict中,key是线程,value是RunLoopCFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);//此处NULL和__CFRunLoops指针都指向NULL,匹配,所以将dict写到__CFRunLoopsif (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {//释放dictCFRelease(dict);}//释放mainrunloopCFRelease(mainLoop);__CFSpinLock(&loopsLock);}//以上说明,第一次进来的时候,不管是getMainRunloop还是get子线程的runloop,主线程的runloop总是会被创建//从字典__CFRunLoops中获取传入线程t的runloopCFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));__CFSpinUnlock(&loopsLock);//如果没有获取到if (!loop) {//根据线程t创建一个runloopCFRunLoopRef newLoop = __CFRunLoopCreate(t);__CFSpinLock(&loopsLock);loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));if (!loop) {//把newLoop存入字典__CFRunLoops,key是线程tCFDictionarySetValue(__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);//如果是kCFRunLoopCommonModesif (modeName == kCFRunLoopCommonModes) {//如果runloop的_commonModes存在,则copy一个新的复制给setCFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;//如果runl _commonModeItems为空if (NULL == rl->_commonModeItems) {//先初始化rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);}//把传入的CFRunLoopSourceRef加入_commonModeItemsCFSetAddValue(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查找modeCFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);//如果_sources0不存在,则初始化_sources0,_sources0和_portToV1SourceMapif (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中都不包含传入的sourceif (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {//如果version是0,则加到_sources0if (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可以通过内核向其端口发送消息来主动唤醒runloopCFDictionarySetValue(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 lockrls->_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找到对应的modeCFRunLoopModeRef 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)回调,处理Source0sourceHandledThisLoop = __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,执行blockelse 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的调用。