RunLoop原理
源代码
RunLoop的获取
主线程的runloop
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();//判断是否需要fork 进程
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
当前线程的runloop
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
//先从TSD中查找有没有相关的runloop信息,有则返回。
//我们可以理解为runloop不光存在与全局字典中,也存在中TSD中。
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
RunLoop与线程之间的关系
// 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为nil的时候,默认是使用主线程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
//将线程和runloop之间关系通过一个全局字典保存起来
//第一次进入的时候,会创建一个全局状态的,首先会为主线程创建一个runloop对象,并塞进该字典中,key是线程ID,value是runloop
__CFLock(&loopsLock);
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
//获取当前线程对应的runloop,如果没有就创建一个新的runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
//这里又使用了CFDictionaryGetValue方法获取loop,目的是为了防止
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
__CFUnlock(&loopsLock);
CFRelease(newLoop);
}
//这边代码的作用:当线程销毁时,顺便也销毁其对应的runLoop
//_CFSetTSD的代码位于CFPlatform.c文件中,传入PTHREAD_DESTRUCTOR_ITERATIONS-1的时候,会清除TSD中的储存着的runloop信息。(void (*)(void *))__CFFinalizeRunLoop 则会清空掉字典中,当前线程与runloop之间的关系
//TSD: Thread-Specific Data 它是线程的一个数据存储单元,和全局变量很像,在线程内部,各个函数可以象使用全局变量一样调用它,但它对线程外部的其它线程是不可见的
if (pthread_equal(t, pthread_self())) {//声明的线程ID和线程自身ID相等
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
通过源代码我们可以知道:
- runloop和线程之间是一一对应的,它们之间的关系保存在一个全局字典以及TSD中。
- 在线程创建的时候,是没有对应的runloop,runloop的创建是在第一次获取的时候,runloop的销毁则发生在线程销毁的时候。
RunLoop的类和相关属性介绍
在CFRunLoop中关于RunLoop的类一共有五个,它们分别是CFRunLoopRef
、CFRunLoopSourceRef
、CFRunLoopObserverRef
、CFRunLoopTimerRef
、CFRunLoopModeRef
struct __CFRunLoop {
CFMutableSetRef _commonModes;//common mode的集合
CFMutableSetRef _commonModeItems;//每个common mode都有的item(source,timer and observer)集合
CFRunLoopModeRef _currentMode;//当前runloop的mode
CFMutableSetRef _modes;//所有的mode的集合
...
};
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;//source0的集合
CFMutableSetRef _sources1;//source1的集合
CFMutableArrayRef _observers;//observer的数组
CFMutableArrayRef _timers;//timer的数组
...
};
//CFRunLoopSourceRef
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits;
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
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);
//当source被添加到RunLoop中后,会调用这个回调
void (*schedule)(void *info, CFRunLoopRef rl, CFStringRef mode);
//当调CFRunLoopSourceInvalidate 函数移除该source的时候,会执 此回调。
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);
//RunLoop处 该Source的时候会执 的回调
void (*perform)(void *info);
} CFRunLoopSourceContext;
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:通过内核对线程发送消息
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;
总结5个类之间的关系:
CFRunLoopRef
对应的结构体中包含了若干Mode,而每个CFRunLoopModeRef
中有包含了若干CFRunLoopSourceRef
、CFRunLoopObserverRef
、CFRunLoopTimerRef
Current Mode
runloop必须指定一个currentMode(_currentMode的赋值在CFRunLoopRunSpecific
函数中,CFRunLoopRunSpecific
函数在CFRunLoopRun
和CFRunLoopRunInMode
中调用),如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
Source
Source有两个版本:Source0和Source1,其中Source0只包含一个指针回调(perform),Source1除了这个指针回调以为还有一个mach_port。match_port是用于内核向线程发送消息的,所以Source1能够主动唤醒RunLoop线程,Source0需要先调CFRunLoopSourceSignal(source)
将其标记为待处 ,然后再调用CFRunLoopWakeUp(runloop)
来唤醒RunLoop以处理该事件。
Common Mode
在iOS下,主线程会预设两个Mode: kCFRunLoopDefaultMode 和 UITrackingRunLoopMode ,并且都被标记为“CommonModes”。
什么是CommonModes?它其实是一个标识符,并不是一个具体的Mode。以addSource为例:
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);
//当Mode为kCFRunLoopCommonModes
if (modeName == kCFRunLoopCommonModes) {
//如果_commonModes存在则获取一份拷贝数据
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
//_commonModeItems如果不存在则创建一个新的集合
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
//将source添加到_commonModeItems
CFSetAddValue(rl->_commonModeItems, rls);
if (NULL != set) {
CFTypeRef context[2] = {rl, rls};
/* add new item to all common-modes */
//调用__CFRunLoopAddItemToCommonModes函数向_commonModes中所有的Mode添加这个source
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
} else {
//此时该Mode不是CommonMode,从runloop中获取当前对应的Mode
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
//当该Mode存在于RunLoop中,并且该Mode的_sources0集合为空
if (NULL != rlm && NULL == rlm->_sources0) {
//创建_sources0集合和_sources1集合
rlm->_sources0 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
rlm->_sources1 = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
rlm->_portToV1SourceMap = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, NULL);
}
//当该Mode存在于RunLoop中,并且_sources0集合和_sources1集合中都不包含该Source
if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
//说明是Source0
if (0 == rls->_context.version0.version) {
CFSetAddValue(rlm->_sources0, rls);
} else if (1 == rls->_context.version0.version) {//说明是Source1
CFSetAddValue(rlm->_sources1, rls);
__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if (CFPORT_NULL != src_port) {
CFDictionarySetValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port, rls);
__CFPortSetInsert(src_port, rlm->_portSet);
}
}
__CFRunLoopSourceLock(rls);
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) {
//如果是Source0,并且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 */
}
}
一个Mode通过将其ModeName可添加到RunLoop的的“commonModes”中。每当RunLoop的内容发生变化时,RunLoop都会自动将_commonModeItems里的Source/Observer/Timer同步到具有“Common”标记的所有Mode里(这句话很重要)。
这里我们做一下拓展,在面试的时候经常被问到的一个问题--为什么列表滑动的时候,NSTimer不执行回调?该如何解决?
默认NSTimer是运行在RunLoop的kCFRunLoopDefaultMode
下,在列表滑动的时候,RunLoop会进入UITrackingRunLoopMode
,因为RunLoop只能运行在一种模式下,所以NSTimer不会执行回调。那如何解决呢?这似乎更简单,现成的API就有添加到CommonModes就可以了,这又是为什么呢?其实不难理解,kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
都已经被标为”Common”属性的。这就解释了为什么添加到CommonModes的时候就可以解决NSTimer的回调问题。
RunLoop运行
RunLoop通过CFRunLoopRun
和CFRunLoopRunInMode
这两个函数运行。
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
//线程就会一直停留在这个do-while的循环里直到result为kCFRunLoopRunStopped或者kCFRunLoopRunFinished
//函数不会主动调用CFRunLoopStop函数(kCFRunLoopRunStopped)
或者将所有事件源移除(kCFRunLoopRunFinished),从这里我们也可以了解,如果runloop的_currentMode值变化,只能退出,然后重新指定一个Mode进入
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
上述两个函数均调用了CFRunLoopRunSpecific
这个函数,那接下来我们分析一下这个函数
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
//是否释放
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
// 首先根据modeName找到对应Mode,如果没有则创建一个新的Mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//如果mode为空或者mode中没有相关的source/timer/observer
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
Boolean did = false;
if (currentMode) __CFRunLoopModeUnlock(currentMode);
__CFRunLoopUnlock(rl);
return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
}
volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
CFRunLoopModeRef previousMode = rl->_currentMode;
rl->_currentMode = currentMode;
int32_t result = kCFRunLoopRunFinished;
//通知Observers:RunLoop即将进入loop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//真正的RunLoop运行方法:__CFRunLoopRun
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//通知Observers: 退出runloop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
再看一下__CFRunLoopRun
的代码(这里的代码很长,只贴关键性的代码)
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm,
CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode)
{
do { //通知观察者,即将处理Timer
if (rlm->_observerMask & kCFRunLoopBeforeTimers)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//通知观察者,即将处理Source
if (rlm->_observerMask & kCFRunLoopBeforeSources)
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//处理加入的Block __CFRunLoopDoBlocks(rl, rlm);
//处理Source0
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm,
stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
//如果被MachPort唤醒,跳转到handle_msg
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer),
&livePort, 0, &voucherState, NULL)) {
goto handle_msg;
}
//通知Observer,即将休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting))
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
__CFRunLoopSetSleeping(rl);
//调 mach_msg 等待接受 mach_port 的消息,线程将进 休眠,直到被唤醒 msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer),
&livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
//通知Observer,即将被唤醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting))
__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//处理消息 handle_msg:;
//处理Timer,处理Source...
//一系列判断,retVal是否为0, 不为0,继续loop
} while (retVal == 0);
}
我们可以看到Runloop内部其实一直在一个循环里面,通过mach_msg()
函数,在用户态和内核态中相互切换,RunLoop调用这个函数去接收消息,如果没有别人发送port消息过来,内核会将线程置于等待状态
RunLoop的面试题分析
以下代码的输出结果
- (void)viewDidLoad {
[super viewDidLoad];
NSInteger number = 1;
NSLog(@"%zd", number);
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{
[self performSelector:@selector(printString) withObject:nil afterDelay:0];
});
number = 3;
NSLog(@"%zd", number);
}
- (void)printString {
NSLog(@"sdasdas");
}
结果:1 3
NSObject的performSelecter:afterDelay:
和performSelector:onThread:
被调用时,实际添加了一个Timer到当前线程的RunLoop中,如果该线程没有RunLoop则不会执行这个方法。
AutoReleasePool在什么么时候释放?
App启动后,主线程的RunLoop被创建了两个Observer,分别在kCFRunLoopEntry
进入的时候和kCFRunLoopBeforeWaiting
即将休眠的时候执行回调。
在进入时,会调用_objc_autoreleasePoolPush()
创建自动释放池。
在即将休眠时,会先调用_objc_autoreleasePoolPop()
,接着再调用_objc_autoreleasePoolPush()
,释放旧池,创建新池。
也就是说:通常在主线程的代码已经被RunLoop自动创建的AutoReleasePool包裹着,因此不会出现内存泄漏。当然,我们也可以创建自己的自动释放池干预某些对象的释放时机,以解决一些内存峰值的问题。
两个线程如何实现交替打印
思路:在A线程的RunLoop即将休眠kCFRunLoopBeforeWaiting
的时候,主动去唤醒B线程的RunLoop,在B线程的RunLoop即将进入休眠的时候,主动唤醒A。代码大致如下:
@interface ViewController ()
@property (nonatomic, strong) NSThread *thread1;
@property (nonatomic, strong) NSThread *thread2;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.thread1 = [[NSThread alloc] initWithTarget:self selector:@selector(thread1Run) object:nil];
self.thread2 = [[NSThread alloc] initWithTarget:self selector:@selector(thread2Run) object:nil];
[self.thread1 start];
[self.thread2 start];
}
- (void)thread1Run {
//添加Observer用来唤醒其他线程的RunLoop
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
if (activity == kCFRunLoopBeforeWaiting) {
NSLog(@"1");
[self performSelector:@selector(weakUpThread) onThread:self.thread2 withObject:nil waitUntilDone:YES];
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
- (void)thread2Run {
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
if (activity == kCFRunLoopBeforeWaiting) {
NSLog(@"2");
[self performSelector:@selector(weakUpThread) onThread:self.thread1 withObject:nil waitUntilDone:NO];
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
CFRelease(observer);
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
- (void)weakUpThread {
CFRunLoopWakeUp(CFRunLoopGetCurrent());
}