通常我们开发iOS app时接触到的是NSRunLoop
,而NSRunLoop
实际上是对苹果的Core Foundation
框架中CFRunLoop
的封装,这次我们直接通过官方文档和Core Foundation
源码学习CFRunLoop
。
Core Foundation
是纯C版本的实现,苹果已经开源了Core Foundation
的源码,相关链接: 官方文档 源码下载
CFRunLoop
头文件中的声明
typedef struct __CFRunLoop * CFRunLoopRef;
__CFRunLoop的定义(只保留了主要属性)
struct __CFRunLoop {
CFMutableSetRef _commonModes;//common mode集合,后边会讲
CFMutableSetRef _commonModeItems;//source集合
CFRunLoopModeRef _currentMode;//当前生效的mode
CFMutableSetRef _modes;//当前runloop的所有mode
struct _block_item *_blocks_head;//block链表头
struct _block_item *_blocks_tail;//block链表尾部
};
可以看到,一个run loop对象包含的元素并不多,其中_commonModes
、_commonModeItems
、_currentMode
、_modes
都跟mode有关,剩余两个属性是一个链表,用来保存block。
那么mode是什么,接下来看一看mode的定义
CFRunLoopMode
如下是CFRunLoopModeRef
的定义:
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFStringRef _name;//mode的名字
Boolean _stopped;//用于退出当前mode的方法
//输入源 source
CFMutableSetRef _sources0;//非port相关事件(如用户点击)
CFMutableSetRef _sources1;//port相关的事件(系统事件)
//监听者 observer
CFMutableArrayRef _observers;
CFIndex _observerMask;//监听者注册的监听事件
//计时器 timer
CFMutableArrayRef _timers;
};
一个mode包含三种类型的对象,sources(CFRunLoopSource
)、timers(CFRunLoopTimer
)和observers(CFRunLoopObserver
),要接受run loop的回调,必须依赖这三种对象。他们和run loop以及mode之间的关系如下:
每一个线程对应一个run loop对象,每一个run loop包含多个mode,每一个mode包含多个source、timer以及observer。
每个source、timer和observer必须添加到一个或多个mode中才能生效,但是run loop同时只能运行一个mode,如果当前运行的mode不是添加的mode,则不会生效。
举个
UIScrollView
的例子主线程的run loop默认运行的是
NSDefaultRunLoopMode
,而当滑动UIScrollView
时,主线程的run loop会切换到UITrackingRunLoopMode
。如果在主线程的
NSDefaultRunLoopMode
中加入了一个NSTimer
,当用户滑动UIScrollView
的时候,主线程会将run loop切换到UITrackingRunLoopMode
,这时这个NSTimer
就不会生效。直到用户停止滑动时,主线程将run loop切换回NSDefaultRunLoopMode
,此时计时器才会生效。
系统定义了一些mode,常见的有
- 默认mode:
NSDefaultRunLoopMode
(Cocoa) /kCFRunLoopDefaultMode
(Core Foundation) - 事件跟踪:
UITrackingRunLoopMode
(Cocoa) - Common Modes:
NSRunLoopCommonModes
(Cocoa) /kCFRunLoopCommonModes
(Core Foundation)
当我们需要执行一些优先级较高的任务时,也可以自定义mode,限制一些低优先级的事件,保证高优先级任务的执行。
每一个Mode通过name
区分,Core Foundation没有对外暴露run loop mode的接口,使用者只需要关心mode的name
即可,如NSDefaultRunLoopMode
、kCFRunLoopDefaultMode
和UITrackingRunLoopMode
,他们都是string
类型(可以通过toll-free bridge转换)。
kCFRunLoopCommonModes
在__CFRunLoop
的定义中,如下两句定义了common modes相关的变量:
CFMutableSetRef _commonModes;//common mode集合
CFMutableSetRef _commonModeItems;//source/timer/observer的集合
Common modes不是一个mode,而是很多mode的集合。被添加到common modes中的mode,会存储在_commonModes
中,同时会将_commonModeItems
中的元素添加到这个mode中,这样_commonModeItems
可以被_commonModes
包含的每一个mode运行时监听到。
从使用者的角度来说,当添加了一个timer到NSRunLoopCommonModes
中,无论当前run loop运行在哪一种mode下,只要这个mode在common modes集合中,这个NSTimer
就会生效。
再拿上面
UIScrollView
举个例子在主线程中,
NSDefaultRunLoopMode
和UITrackingRunLoopMode
这两个mode被系统加入了common modes中。这意味着,我们如果将NSTimer
加入到common modes中,也就是添加到kCFRunLoopCommonModes
,此时无论用户是否滑动UIScrollView
,这个NSTimer
都会生效。
这里有两种方式与common modes打交道,一个是添加自定义mode到common modes中,另一个是添加sources/timers/observers到kCFRunLoopCommonModes
,来看看对应的实现
1. CFRunLoopAddCommonMode
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
//判断common modes是否已经存在该mode
if (!CFSetContainsValue(rl->_commonModes, modeName)) {
//获取common modes set
CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
//添加mode进common modes set
CFSetAddValue(rl->_commonModes, modeName);
if (NULL != set) {
CFTypeRef context[2] = {rl, modeName};
//将common mode items中的每一个item添加到传入的mode中
CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
}
}
}
当调用CFRunLoopAddCommonMode
函数时,会将新的mode加入到该runloop的common modes集合中,同时,会将当前common mode items中的所有元素,添加到新的mode中。
如此一来,无论run loop被切换到哪一个mode,只要这个mode被加入到kCFRunLoopCommonModes
中,就可以响应那些被添加到kCFRunLoopCommonModes
的sources/timers/observers。
2. CFRunLoopAddSource
/CFRunLoopAddObserver
/CFRunLoopAddTimer
可以在将source/timer/observer三种对象加入到run loop时传入kCFRunLoopCommonModes
参数,这样run loop运行在common modes的mode时,这些source/timer/observer就会生效。这里仅放一个CFRunLoopAddTimer
中与common modes有关的代码,其他两种(source/observer)代码都是类似的。
void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
//判断是否是将timer加入到kCFRunLoopCommonModes
if (modeName == kCFRunLoopCommonModes) {
//获取common modes集合
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
//如果common modes items为空,则创建一个common modes items的集合
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
//将传入的timer source加入到common mode items中
CFSetAddValue(rl->_commonModeItems, rlt);
//将timer添加到common modes中的每一个mode中
if (NULL != set) {
CFTypeRef context[2] = {rl, rlt};
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
}
} else {
//非common mode的操作
}
}
将一个timer加入到kCFRunLoopCommonModes
中后,会将这个timer加入到common mode items里,同时也会将这个timer加入到common modes集合中的每一个mode中,也就是说,该函数会帮你更新所有在common modes集合中的mode,这样就达到了无论运行在任意一个common mode时,都可以使这个timer生效。
CFRunLoopSource/CFRunLoopTimer/CFRunLoopObserver
我们可以通过将source/timer/observer这三种对象加入到run loop中,当有事件发生时,通过回调接受通知。在加入到run loop时,必须指定一个mode。当然也可以从一个run loop中移除上述三种对象。
CFRunLoopSourceRef
Input source是事件发生的来源,通常产生异步事件,比如消息到达网络端口或者用户执行的操作。它的定义如下:
typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;//toll-free bridge
struct __CFRunLoopSource {
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0;//source0
CFRunLoopSourceContext1 version1;//source1
} _context;
};
在CFRunLoopSourceRef
中,最主要的是两个变量CFRunLoopSourceContext
和CFRunLoopSourceContext1
,它们用union
来声明,意味着有两种不同的source,source0和source1。
source0
source0是一般响应应用程序事件,例如按钮的响应事件。当需要发送source0事件时,调用CFRunLoopSourceSignal
函数将这个source标记为待触发,但是该函数不能唤醒runloop,需要再调用CFRunLoopWakeUp
方法唤醒对应的run loop,这个source0事件才会被触发。Core Foundation中的CFSocket
使用的是source0的方式实现。定义如下:
typedef struct {
CFIndex version;//0代表是source0
void (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
void (*perform)(void *info);
} CFRunLoopSourceContext;
它包含三个回调,
-
schedule
:当添加source0到run loop时,会调用一次schedule
回调方法; -
perform
:当触发source0时,会调用perform
回调方法; -
cancel
:当移除或者run loop销毁时,会调用cancel回调方法。
source1
source1由run loop和内核管理,使用mach port进行通信,用于通过内核进行进程间通信,也可以用于线程间的通信。source1能够将run loop唤醒。Core Foundation中的CFMachPort
和CFMessagePort
通过source1的方式实现。定义如下:
typedef struct {
CFIndex version;//1代表source1
mach_port_t (*getPort)(void *info);
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#endif
} CFRunLoopSourceContext1;
其中
-
getPort
函数指针提供一个获取port的函数 -
perform
函数是回调函数
有趣的是,
CFMessagePort
通常用于进程间通信,如iOS越狱开发,常用的场景是前端有一个UI程序用于界面展示,后端有一个daemo精灵程序用于任务处理。而官方文档中有提到,CFMessagePort
已不能再iOS7之后的系统中使用。来源。
CFRunLoopSourceRef
的使用
Core Foundation提供了提供了与input sourc交互的函数
-
CFRunLoopSourceGetContext
:创建source0或者source1变量 -
CFRunLoopSourceCreate
:创建CFRunLoopSourceRef
-
CFRunLoopAddSource
:添加CFRunLoopSourceRef
到run loop中
CFRunLoopTimerRef
CFRunLoopTimerRef
是和NSTimer
toll-free bridge的计时器。Run loop timer并不一定可靠,如果加入的mode没有运行或者当前run loop在执行一段耗时的操作,run loop timer可能不会被触发。而且Run loop timer的触发时间依赖于计划的时间间隔,而不是实际运行的时间间隔,比如一个5秒重复的计时器,在第二次时延时了2秒,那么第三次的执行时间不会改变,仍然是第15秒时执行。
Run loop timer可以同时加入到多个run loop mode中,但中只会在第一个加入的run loop内生效。
CFRunLoopTimerRef
主要包含了interval
参数和Callback
回调方法。
Run loop timer关于时间计算的机制以后再深入了解
CFRunLoopObserverRef
CFRunLoopObserverRef
是一个观察者,它包含了一个回调指针和一个Activity
参数,用于表明接受的run loop事件,具体事件定义如下,当run loop处于不同状态时,会通知obsever。
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
};
blocks
在__CFRunLoop
的定义中,还有两个关于block链表的定义
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
Run loop还提供了一个CFRunLoopPerformBlock
函数用于添加block
,和source0类似,这个方法不会主动唤醒run loop,需要调用CFRunLoopWakeUp
函数主动唤醒run loop。
CFRunLoopPerformBlock
的定义如下
void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void))
在run loop运行的每一次循环中,会多次检查当前是否有需要执行的block,如果有则会执行传入的block
。
CFRunLoopGet
在通常开发时,我们通常不需要关心run loop的生命周期。系统会自动在主线程帮我们创建run loop对象。可以通过如下接口获得主线程的run loop对象,该方法返回一个CFRunLoopRef
实例。
CFRunLoopRef CFRunLoopGetMain(void);
当我们创建一个新的线程时,默认是没有初始化run loop对象的,需要调用函数获取当前线程的run loop对象。
CFRunLoopRef CFRunLoopGetCurrent(void);
如此看来,创建run loop对象的函数就是Core Foundation内部实现的了,CFRunLoopGetMain
和CFRunLoopGetCurrent
这两个函数内部都会调用同一个方法_CFRunLoopGet0(pthread_t t)
,来看下具体的代码。
这里简单提一下TSD(thread specific data)的概念,在多线程环境中,因为数据空间是共享的,所以全局变量也为所有线程所共有。所以当需要一个仅在当前线程中可以访问的数据时,使用TSD来存储。TSD存储的数据仅在当前线程有效,但是可以跨函数访问。
run loop对象就存储在TSD中。
CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
//__CFRunLoops是全局保存runloop对象的dict,首次运行时初始化该dict
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
//创建全局ditc,并添加主线程的runloop 对象
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//通过__CFRunLoopCreate方法创建主线程的run loop对象
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
//将dict与__CFRunLoops指针互换,然后释放ditc
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
}
//通过线程对象获取对应的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
//如果该线程还没有创建runloop对象,那么初始化该线程的runloop对象
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
}
//判断是否获取当前线程的runloop对象
if (pthread_equal(t, pthread_self())) {
//将run loop对象存放到TSD中
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
//在这里设置销毁的回调方法,当线程生命周期结束时销毁runloop对象
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
该函数的流程如下
- 第一次进入时,会创建一个全局的dict,用于存储每一个线程对应的run loop对象,同时也会初始化主线程的run loop对象。
- 根据传入的线程获取对应的run loop对象,若为空,则创建一个run loop对象,并添加到全局dict中。同时也会将这个run loop放到对应线程的TSD中并设置一个线程结束时的销毁函数回调。
通过上面的函数,我们可以获取到当前线程的run loop对象。在执行前面所讲的添加source/timer/observers函数时,都需要传入run loop对象。
系统会启动主线程的run loop的运行,对于其他线程,需要我们在获取run loop对象后主动启动。
CFRunLoopRun
首先通过一张图,了解下CFRunLoopRun的主要逻辑。
PS:图中左边的source0(port)应该是source1(port)
在Core Foundation提供了两个函数供我们启动run loop,CFRunLoopRun
和CFRunLoopRunInMode
,函数声明如下:
//无参数,直接启动run loop,运行在default mode
void CFRunLoopRun(void);
//设置run loop运行的mode,有效期以及是否在运行source 0事件后直接退出,返回值为run loop退出的原因字段
SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
这两个函数都会调用CFRunLoopRunSpecific
函数,该函数实现如下,注释中的数字对应图中的顺序
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
//判断mode中是否有source,timer或者block事件,如果没有,run loop会立即退出
if (__CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
return kCFRunLoopRunFinished;
}
int32_t retVal = kCFRunLoopRunFinished;
//1. run loop即将进入,通知Observers
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
do {
//2. 通知观察者,即将触发计时器
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//3. 通知观察者即将触发source0(非port based)输入源
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//处理blocks
__CFRunLoopDoBlocks(rl, rlm);
//4. 处理source0,也就是非port based输入源
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//处理完source0后会再处理下blocks
__CFRunLoopDoBlocks(rl, rlm);
}
//5. 检查是否有需要处理的source1事件,通常是系统级事件,倒数第三个参数传0表示立即返回
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
//跳转到handle_msg直接去处理source1事件
goto handle_msg;
}
//没有source1则直接进入睡眠
//6. 通知观察者Runloop即将进入睡眠
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//7. 调用系统内核方法mach_msg,切换到内核态接受消息
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
//当有source1,timer或者手动唤醒时,会退出睡眠态
//8. 通知观察者Runloop退出等待
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
handle_msg:;
//9. 退出睡眠后,需要处理些事件
//计时器需要触发
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
//触发计时器
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
//计算下一次触发的时间
__CFArmNextTimerInMode(rlm, rl);
}
} else if (livePort == dispatchPort) {
//如果有dispatch到main_queue的block,执行block。
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
} else {//source1事件
__CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
}
//再次执行一次block回调
__CFRunLoopDoBlocks(rl, rlm);
//判断是否需要退出run loop
if (sourceHandledThisLoop && stopAfterHandle) {
//启动run loop时设置了执行source0后立即退出
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
//启动run loop时设置了超时时间
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
//run loop被设置为停止
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
//run loop mode被设置为停止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
//当前mode被移除了
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);
//10. run loop已经退出,通知observers
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return retVal;
}
通过代码可以了解到
- run loop通过do…while循环实现,只要不满足退出的条件,run loop就会睡眠或者运行。
- run loop需要添加source,timer或者block事件才能运行,否则会直接退出
- run loop进入休眠时调用
mach_msg
函数切换到内核态(当我们在app运行时点击暂停,就可以看到调用栈停留在mach_msg_trap()
这个方法)
PerformSelecter
当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。
当调用 performSelector:onThread: 时,实际上其会创建一个source0 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。
苹果对RunLoop的应用
了解了RunLoop的实现,我们看一看苹果如何使用RunLoop的。该节主要参考深入理解RunLoop,这里仅做部分展开。
在viewDidLoad
方法中增加断点,打印[NSRunLoop currentRunLoop]
可以打印出当前线程的runLoop
对象的一些详细信息
<CFRunLoop
current mode = kCFRunLoopDefaultMode,
common modes = {
"UITrackingRunLoopMode"
"kCFRunLoopDefaultMode"
}
common mode items = {
//source 0
CFRunLoopSource {
order = -1, version = 0, callout = PurpleEventSignalCallback
}
CFRunLoopSource {
order = -1, version = 0, callout = __handleEventQueue
}
CFRunLoopSource {
order = 0, version = 0, callout = FBSSerialQueueRunLoopSourceHandler
}
CFRunLoopSource {
order = -2, version = 0, callout = __handleHIDEventFetcherDrain
}
//source 1
CFRunLoopSource {
order = 0, port = 43275
}
CFRunLoopSource {
order = -1, version = 1, callout = PurpleEventCallback
}
CFRunLoopSource {
order = 0, port = 42755
}
//UI绘制相关
CFRunLoopObserver {
activities = 0xa0, order = 1999000, callout = _beforeCACommitHandler
}
CFRunLoopObserver {
activities = 0xa0, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv
}
CFRunLoopObserver {
activities = 0xa0, order = 2001000, callout = _afterCACommitHandler
}
//自动释放池(auto release pool)相关
CFRunLoopObserver {
activities = 0x1, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
}
CFRunLoopObserver {
activities = 0xa0, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
}
CFRunLoopObserver {
activities = 0x20, order = 0, callout =_UIGestureRecognizerUpdateObserver
}
}
modes = {
//RunLoop包含的mode以及mode中的observers/sources/timers
}
AutoreleasePool
苹果在主线程RunLoop的CommonModes中注册了两个Observer,其中回调函数都是_wrapRunLoopWithAutoreleasePoolHandler
。
<CFRunLoopObserver>{
order = -2147483647, activities = 0x1, callout = _wrapRunLoopWithAutoreleasePoolHandler
}
<CFRunLoopObserver>{
order = 2147483647, activities = 0xa0, callout = _wrapRunLoopWithAutoreleasePoolHandler
}
第一个Observer设置了最高优先级-2147483647,在进入RunLoop时会触发(0x1)。
第二个Observer设置了最低优先级2147483647,在RunLoop进入睡眠或者退出时触发(0xa0)。
_wrapRunLoopWithAutoreleasePoolHandler
通过在XCode中添加Symbolic Breakpoint增加断点,查看_wrapRunLoopWithAutoreleasePoolHandler
对应汇编的代码。
启动App后,会进入该断点,可以看到代码中有两处地方会跳转到其他函数,分别是NSPushAutoreleasePool
和NSPopAutoreleasePool
,根据名字可以看出,一个是autlreleasePool的创建,另一个是autlreleasePool的释放,推测在该函数中会判断RunLoop当前的状态,然后执行不同的函数。
然后我们继续对NSPushAutoreleasePool
和NSPopAutoreleasePool
添加断点,最终调用函数分别是objc_autoreleasePoolPush
和objc_autoreleasePoolPop
。
第一个Observer在进入RunLoop时,创建autoreleasePool,其order=-2147483647
保证了是所有回调之前调用。
第二个Observer在RunLoop休眠时,首先释放autoreleasePool,然后创建autoreleasePool;在RunLoop退出时,释放autoreleasePool,其order=2147483647
保证了是所有回调之后调用。
总结
本文主要对Core Foundation
框架中的RunLoop源码进行学习,了解了RunLoop的实现原理,后续会对RunLoop的应用进一步探索研究。