什么是RunLoop
顾名思义:运行循环,在程序的运行过程中去循环的做些事情。
RunLoop 实际上是一个对象,这个对象在循环中用来处理程序运行过程中出现的各种事件。比如:
1. 定时器(Timer)、PerformSelector(到指定的线程/model/延长执行的这些都与runloop相关)
2. GCD Async Main Queue(一般是从GCD的子线程回到主线程会调用到这个方法)
3. 事件响应、手势识别、界面刷新(source0/source1的这些事件)
4. 网络请求(子线程的保活)
5. AutoreleasePool
以上的这些场景基本都与runloop息息相关。RunLoop在有事的时候去运作,在没有事件处理的时候,会使线程进入睡眠模式,从而节省 CPU 资源,提高程序性能。
那在我们的程序中runloop是如何表现的呢?
我们先来看看对于我们的程序,runloop的作用。
int main(int argc, char * argv[]) {
@autoreleasepool {
NSLog(@"hello world!");
}
return 0;
}
如果没有runloop,执行完nslog打印代码后,会即将退出程序。
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
这是有runloop的时候我们的main函数,其实内部逻辑相当于:
int main(int argc, char * argv[]) {
@autoreleasepool {
int result = 0;
do {
// 睡眠中去等待消息
int message = sleep_and_wait();
// 获取消息,看是否有消息需要处理
result = process_message(message);
} while(result == 0);
}
return 0;
}
runloop的内部逻辑,就是一个do-while循环,让程序不会马上退出,而是保持运行状态。当有事件处理的时候就去处理,没有事件的时候就去休眠,节省cpu的资源。
runloop对象
iOS中我们无法主动去创建runloop
,我们只能去通过API去获取runloop
。iOS中目前有2套API来让我们访问和使用RunLoop
Foundation:NSRunLoop
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
Core Foundation:CFRunLoopRef
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
NSRunLoop
和CFRunLoopRef
都代表着RunLoop
对象
NSRunLoop
是基于CFRunLoopRef
的一层OC包装
CFRunLoopRef是开源的
我们主要看CFRunLoopRef的源码,在runloop中相关5个比较重要的对象类:
1. CFRunLoopRef
2. CFRunLoopModeRef
3. CFRunLoopSourceRef
4. CFRunLoopTimerRef
5. CFRunLoopObserverRef
我们主要看两个类分析:__CFRunLoop
和__CFRunLoopMode
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;
};
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
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 */
};
我们简化一些两个类的内容:__CFRunLoop
和__CFRunLoopMode
typedef struct __CFRunLoop * CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
};
struct __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
};
CFRunLoopModeRef
代表RunLoop
的运行模式
从上面的代码中也能看出: 一个RunLoop
包含若干个Mode
,每个Mode
又包含若干个Source0/Source1/Timer/Observer
RunLoop的获取逻辑
上面我们也说到了在oc中获取runloop的两种方式,那他们的底层是如何操作的呢?
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;
}
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
return _CFRunLoopGet0(pthread_self());
}
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
__CFSpinUnlock(&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);
__CFSpinLock(&loopsLock);
}
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) {
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;
}
从上面的源码中我们可以分析出:
1 . 每条线程都有唯一的一个与之对应的RunLoop对象
2 . RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
3 . 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
4 . RunLoop会在线程结束时销毁
5 . 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
RunLoop对象的运行run
上面我们看到了RunLoop对象内部如何创建的过程,然后runloop要怎么run起来呢?
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
看了源码我们也能看到和我们开篇说的那样:
RunLoop
其实内部就是do-while循环
,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer
),通过判断result
的值实现的。所以 可以看成是一个死循环
。如果没有RunLoop
,UIApplicationMain
函数执行完毕之后将直接返回,就是说程序一启动然后就结束。
同时我们也能看到:
kCFRunLoopDefaultMode
,默认情况下,runLoop
是在这个mode
下运行的,