什麼是Runloop
?
Runloop
是通過內部維護的事件循環來對事件和消息進行管理的一種機制。當沒有消息需要處理的時候,線程進入休眠以避免佔用資源,有消息需要處理時,立即被喚醒。
runloop
循環不是單獨的do-while
循環,而是發生一個用戶態到內核態切換,以及內核態到用戶態切換。它維護的事件循環可以用來不斷的處理消息和事件,當沒有消息和事件需要處理時會從用戶態切換到內核態,由此可以用來休眠線程,避免資源佔用。當有消息需要處理時會從內核態切換到用戶態,當前線程會被喚醒,所以狀態切換纔是runloop
的關鍵。
main
函數爲何能保持不退出?
在main
函數中,會調用UIApplicationMain
函數,在內部會啓動主線程的Runloop
,可以不斷的接收消息,比如點擊屏幕事件,滑動列表以及處理網絡請求的返回等接收消息後對事件進行處理,處理完之後,就會繼續等待。
iOS
中提供了兩套Runloop
接口,一個是NSRunLoop
基於Objective-C
,在Foundation
框架中,另一個是CFRunLoopRef
基於C
,在CoreFoundation
中。而NSRunLoop
是對CFRunLoopRef
的封裝,兩者接口基本都是對應的。CFRunLoopRef runloop = [nsrunloop getCFRunLoop]
可以獲取對應的CFRunLoopRef
。通過一個表格來對比一下:
特徵 | NSRunLoop | CFRunLoopRef |
---|---|---|
所屬框架 | Objective-C/Foundation | C/CoreFoundation |
獲取Runloop | [NSRunLoop currentRunLoop] [NSRunLoop mainRunLoop] |
CFRunLoopGetCurrent() CFRunLoopGetMain() |
Source事件 | addPort:forMode: removePort:forMode: |
CFRunLoopAddSource(...) CFRunLoopRemoveSource(...) |
Timer事件 | addTimer:forMode: | CFRunLoopAddTimer(...) |
Observer事件 | CFRunLoopAddObserver(...) CFRunLoopRemoveObserver(...) |
1. __CFRunLoop相關基本數據結構
struct __CFRunLoop {
...
pthread_t _pthread;//對應的線程
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
...
};
__CFRunLoop
是runloop
本身:typedef struct __CFRunLoop *CFRunLoopRef
。__CFRunLoop
對應多個__CFRunLoopMode
。
struct __CFRunLoopMode {
...
CFStringRef _name;
CFMutableSetRef _sources0;//
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
...
};
__CFRunLoopMode
是runloop的運行模式:typedef struct __CFRunLoopMode *CFRunLoopModeRef
。每一個__CFRunLoopMode
又包含多個_sources0、_sources1、_observers、_timers事件。_sources0:非基於Port的,也就是用戶主動發出的事件。_sources1:基於Port的,也就是系統內部的消息事件。_observers:觀察者。
_timers:定時器事件。
相關的類的成員變量與關係:
2.NSRunLoop的創建
[NSRunLoop mainRunLoop]
對應底層的CFRunLoopGetMain()
,[NSRunLoop currentRunLoop]
對應底層的CFRunLoopGetCurrent()
,內部都是通過CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t)
獲取的runloop
,分別傳入pthread_main_thread_np()
和pthread_self()
也就是主線程和當前線程的id。
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
if (pthread_equal(t, kNilPthreadT)) {
//如果外部傳入無效的0,則將主線程ID賦值給t。
t = pthread_main_thread_np();
}
__CFLock(&loopsLock);
if (!__CFRunLoops) {
//如果__CFRunLoops爲空,則創建主線程對應的runloop
__CFUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
//__CFRunLoopCreate做線程的初始化
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 將mainLoop保存到dict中,以線程id爲key,mainLoop爲value
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
//將dict中的內容複製到__CFRunLoops地址上
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
//通過線程id獲取runloop
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
if (!loop) {
//如果獲取到的爲空,則直接創建
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
if (!loop) {
//如果創建後還不能獲取到則使用剛纔創建的,並將newLoop保存到__CFRunLoops中
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);
}
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;
}
在__CFRunLoopCreate
中通過_CFRuntimeCreateInstance
實例華再進行其他變量的一些初始化。其中loop->_pthread = t;
將線程id
綁定到了runloop
上。runloop
通過線程id
去查找,如果沒有則進行創建並將線程id
綁定到runloop
上,通過這個規則我們知道:線程與runloop
一一對應;線程不一定都有runloop
,首個runloop
創建時會檢查__CFRunLoops
是否爲空,爲空則先創建主線程的runloop
,再創建指定線程的runloop
。