寫在前面
由于文章比較長在抛,簡書沒有目錄业簿,讀起來不方便眉踱。建議看有目錄版RunLoop從源碼到應(yīng)用全面解析——帶目錄版
在開始之前有必要重點說明一下:現(xiàn)在開發(fā)中用到的所有的內(nèi)容其實早在官方文檔及相關(guān)API中已經(jīng)介紹過袄简,不要認(rèn)為大神有多牛逼钩骇,他只是比你先閱讀官方文檔及使用維基百科豺谈,StackOverFlow而已郑象。所以在你開始研究某個知識點之前看這些東西遠(yuǎn)比去讀別人消化過得有用得多。一句話學(xué)會如何學(xué)習(xí)才是核心競爭力的關(guān)鍵茬末。
理解RunLoop
深入理解RunLoop這是國內(nèi)對runloop寫得非常非常好的文章厂榛。本文除了源碼分析部分,也很多地方參考了其中的內(nèi)容丽惭。非常感謝作者击奶!
從Event Loop開始談起
RunLoop源碼在CoreFoundation里面,下載地址如下:
一般情況下责掏,一個線程執(zhí)行完之后就會停止柜砾。為了保證線程能隨時處理事件并不退出,于是最簡單的想到就是一個for循環(huán)换衬。
function loop() {
initialize();
do {
var message = get_next_message();
process_message(message);
} while (message != quit);
}
上面這種模型叫做事件循環(huán)局义,實現(xiàn)這種模型的關(guān)鍵點就是如何在沒有消息到來的情況下休眠以避免系統(tǒng)資源的占有,消息一到來立刻恢復(fù)冗疮。
Runloop就是用來處理上面提到的事件及消息萄唇,執(zhí)行上面的Event Loop模型。線程執(zhí)行了這個函數(shù)之后就會一直處于這個函數(shù)內(nèi)部术幔,接受消息——》等待消息——》處理消息的循環(huán)中另萤。直到循環(huán)結(jié)束(傳入quit),然后函數(shù)返回诅挑。
Runloop內(nèi)部數(shù)據(jù)結(jié)構(gòu)
在進(jìn)行RunLoop的執(zhí)行過程分析之前四敞,需要熟悉內(nèi)部的各個數(shù)據(jù)結(jié)構(gòu)。在 CoreFoundation 里面關(guān)于 RunLoop 有5個類拔妥,這幾個類都是結(jié)構(gòu)體指針忿危,對應(yīng)后面會講到的結(jié)構(gòu)體。
- CFRunLoopRef:對外部暴露的對象没龙,外界通過CFRunLoopRef的接口來管理整個Runloop铺厨。
- CFRunLoopModeRef:
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
他們之間的關(guān)系(一對多模式)可以如下圖(來源于網(wǎng)上)所示缎玫,后面會更加詳細(xì)的說明這種關(guān)系。
接下來通過源碼來了解其中數(shù)據(jù)結(jié)構(gòu)解滓,也是對上面這種圖的進(jìn)一步解釋赃磨。
CFRunLoop
對應(yīng)CFRunLoopRef,其數(shù)據(jù)結(jié)構(gòu)如下洼裤。
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; //runloop對應(yīng)的線程
uint32_t _winthread;
CFMutableSetRef _commonModes;//存儲的是字符串邻辉,記錄所有標(biāo)記為common的mode
CFMutableSetRef _commonModeItems;//存儲所有commonMode的item(source、timer腮鞍、observer)
CFRunLoopModeRef _currentMode;//當(dāng)前運行的mode
CFMutableSetRef _modes;//存儲的是CFRunLoopModeRef值骇,
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
從上面可以看出一個RunLoop包含一個線程,也就是和線程是一一對應(yīng)的移国;以及若干個Mode雷客、若干個commonModeItem,還有一個當(dāng)前運行的CurrentMode桥狡。如果在RunLoop中需要切換 Mode搅裙,只能退出 Loop,再重新指定一個 Mode 進(jìn)入裹芝。這樣做主要是為了分隔開不同組的 Source/Timer/Observer部逮,讓其互不影響。
重點說一下modes嫂易,在主線程的runloop中存在很多model兄朋,但是runloop在一個時間點只能在其中一種model下。
下圖是來至維基百科中對runloop中的Model說明:
但是我在模擬器中測試下只看到了UITrackingRunLoopMode怜械、GSEventReceiveRunLoopMode颅和、kCFRunLoopDefaultMode、kCFRunLoopCommonMode四種Model缕允。
CFRunLoopMode
對應(yīng)CFRunLoopModeRef峡扩,結(jié)構(gòu)如下
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];
//幾種事件,下面這四個字段障本,在蘋果官方文檔里面稱為Item教届。runloop中有個commomitems字段,里面就是保持的下面這些內(nèi)容驾霜。
CFMutableSetRef _sources0; //sources0
CFMutableSetRef _sources1; //sources1
CFMutableArrayRef _observers; //觀察者
CFMutableArrayRef _timers; //定時器
CFMutableDictionaryRef _portToV1SourceMap; //字典 key是mach_port_t案训,value是CFRunLoopSourceRef
__CFPortSet _portSet; //保存所有需要監(jiān)聽的port,比如_wakeUpPort粪糙,_timerPort都保存在這個數(shù)組中
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 */
};
從上可以看出一個CFRunLoopMode對象有一個name强霎,若干source0、source1蓉冈、timer城舞、observer和若干port轩触,其中source,timer椿争,observer 數(shù)據(jù)結(jié)構(gòu)被統(tǒng)稱為 mode item。上面提到的那幾種model(UITrackingRunLoopMode熟嫩、GSEventReceiveRunLoopMode秦踪、kCFRunLoopDefaultMode、kCFRunLoopCommonMode)掸茅,其實就是這里的name椅邓。
只能通過 mode的name字段(也就是字符串,前面提到的kCFRunLoopDefaultMode 和 UITrackingRunLoopMode) 操作內(nèi)部的 Mode昧狮,當(dāng)你傳入一個新的 mode name 但 RunLoop 內(nèi)部沒有對應(yīng) mode 時景馁,RunLoop會自動幫你創(chuàng)建對應(yīng)的 CFRunLoopModeRef。對于一個 RunLoop 來說逗鸣,其內(nèi)部的 mode 只能增加不能刪除合住。
source、timer撒璧、observer可以在多個model中注冊透葛,但是只有runloop當(dāng)前的currentMode下的source、timer卿樱、observer才可以運行僚害。
Model暴露給外面管理model 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);
CFRunLoopSource
對應(yīng)CFRunLoopModeRef,其結(jié)構(gòu)如下
struct __CFRunLoopSource {
CFRuntimeBase _base;
uint32_t _bits; //用于標(biāo)記Signaled狀態(tài)繁调,source0只有在被標(biāo)記為Signaled狀態(tài)萨蚕,才會被處理
pthread_mutex_t _lock;
CFIndex _order; /* immutable */
CFMutableBagRef _runLoops;
union {
CFRunLoopSourceContext version0; /* immutable, except invalidation */
CFRunLoopSourceContext1 version1; /* immutable, except invalidation */
} _context;
};
Source分為Source、Observer蹄胰、Timer三種岳遥,他們統(tǒng)稱為modeItem。
__CFRunLoopSource是事件產(chǎn)生的地方裕寨。Source有兩個版本:Source0 和 Source1寒随。
- source0 只包含了一個回調(diào)(函數(shù)指針),source0是需要手動觸發(fā)的Source帮坚,它并不能主動觸發(fā)事件妻往,必須要先把它標(biāo)記為signal狀態(tài)。使用時试和,你需要先調(diào)用 CFRunLoopSourceSignal(source)讯泣,將這個 Source 標(biāo)記為待處理,也就是通過
uint32_t _bits
來實現(xiàn)的阅悍,只有_bits標(biāo)記Signaled狀態(tài)才會被處理好渠。然后手動調(diào)用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop昨稼,讓其處理這個事件。 - source1 包含了一個 mach_port 和一個回調(diào)(函數(shù)指針)拳锚,被用于通過內(nèi)核和其他線程相互發(fā)送消息假栓。這種 Source 能主動喚醒 RunLoop 的線程。簡單來說就是更加偏向于底層霍掺。
以下是source0的結(jié)構(gòu)體:
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, CFStringRef mode);//當(dāng)source加入到model觸發(fā)的回調(diào)
void (*cancel)(void *info, CFRunLoopRef rl, CFStringRef mode);//當(dāng)source從runloop中移除時觸發(fā)的回調(diào)
void (*perform)(void *info);//當(dāng)source事件被觸發(fā)時的回調(diào)匾荆,使用CFRunLoopSourceSignal方式觸發(fā)。
} CFRunLoopSourceContext;
以下是source1的結(jié)構(gòu)體:
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);//當(dāng)source被添加到mode中的時候杆烁,從這個函數(shù)中獲得具體mach_port_t牙丽。
void * (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
void * (*getPort)(void *info);
void (*perform)(void *info);
#endif
} CFRunLoopSourceContext1;
source1除了多個了getPort。其余的字段含義和source0相同兔魂。作用就是當(dāng)source被添加到mode中的時候烤芦,從這個函數(shù)中獲得具體mach_port_t。
CFRunLoopTimer
對應(yīng)RunLoopTimerRef析校,結(jié)構(gòu)如下:
struct __CFRunLoopTimer {
CFRuntimeBase _base;
uint16_t _bits; //標(biāo)記fire狀態(tài)
pthread_mutex_t _lock;
CFRunLoopRef _runLoop; //添加該timer的runloop
CFMutableSetRef _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 */
};
它和 NSTimer 是toll-free bridged 的(資料可以看這里),可以混用智玻。其包含一個時間長度和一個回調(diào)(函數(shù)指針)绰播。當(dāng)其加入到 RunLoop 時,RunLoop會注冊對應(yīng)的時間點尚困,當(dāng)時間點到時蠢箩,RunLoop會被喚醒以執(zhí)行那個回調(diào)。
根據(jù)上面的分析t一個timer可能會在多個mode中存在事甜。
CFRunLoopObserver
對應(yīng)CFRunLoopObserverRef谬泌,結(jié)構(gòu)如下
struct __CFRunLoopObserver {
CFRuntimeBase _base;
pthread_mutex_t _lock;
CFRunLoopRef _runLoop;
CFIndex _rlCount;
CFOptionFlags _activities; /* immutable */
CFIndex _order; /* immutable */
CFRunLoopObserverCallBack _callout; /* immutable 設(shè)置回調(diào)函數(shù)*/
CFRunLoopObserverContext _context; /* immutable, except invalidation */
};
CFRunLoopObserver是觀察者,可以觀察RunLoop的各種狀態(tài)逻谦,每個 Observer 都包含了一個回調(diào)(也就是上面的CFRunLoopObserverCallBack函數(shù)指針)掌实,當(dāng) RunLoop 的狀態(tài)發(fā)生變化時,觀察者就能通過回調(diào)接受到這個變化邦马。狀態(tài)定義在_CF_OPTIONS:
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即將進(jìn)入run loop
kCFRunLoopBeforeTimers = (1UL << 1), //即將處理timer
kCFRunLoopBeforeSources = (1UL << 2),//即將處理source
kCFRunLoopBeforeWaiting = (1UL << 5),//即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//被喚醒但是還沒開始處理事件
kCFRunLoopExit = (1UL << 7),//run loop已經(jīng)退出
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
下面是回調(diào)函數(shù)的原型:
typedef void (*CFRunLoopObserverCallBack)(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info);
小結(jié)
根據(jù)上面的數(shù)據(jù)結(jié)構(gòu)贱鼻,總結(jié)出如下內(nèi)容。
一個model中有多個item滋将,這些item由source邻悬、observe、timer組成随闽。對于我們來講用的最多的應(yīng)該是observe和timer父丰,常常通過回調(diào)來得知當(dāng)前runloop的狀態(tài),進(jìn)行來優(yōu)化應(yīng)用程序(比如監(jiān)控在waiting狀態(tài)下掘宪,這個時候做一些優(yōu)化的事情)蛾扇。其次設(shè)置定時器執(zhí)行定時任務(wù)也是很常見的攘烛。
一個runloop包含了多個model,但是runloop在一個時間點只會處于一種model(kCFRunLoopDefaultMode镀首、UITrackingRunLoopMode坟漱、)狀態(tài)下也即是currentModel,如果該當(dāng)前應(yīng)用狀態(tài)在另一種mode下更哄,則該model下的item(source芋齿、observe、timer)就不會工作竖瘾。
runloop其中有一個commomModes的數(shù)組沟突,里面保存的是被標(biāo)記為common的model花颗。這種標(biāo)記為common的model有種特性捕传,那就是當(dāng) RunLoop 的內(nèi)容發(fā)生變化時,RunLoop 都會自動將 commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 標(biāo)記的所有Model里扩劝。可以這樣理解庸论,runloop中的_commonModeItems由被標(biāo)記為common的model下得各個item(source、observe棒呛、timer)組成聂示。
通過上面三點來理解為什么NSTimer在添加到runloop中的時候要用NSRunLoopCommonModes最為參數(shù)了。這里簡單說一下簇秒。
首先Runloop初始化的時候的會把名字為kCFRunLoopDefaultMode鱼喉、UITrackingRunLoopMode的model加入到common modesls數(shù)組里面,標(biāo)記為common mode趋观,上面提到過common mode的特性扛禽。NSTimer對應(yīng)于上面提到的model中的item中的timer,并且NSTimer創(chuàng)建好之后默認(rèn)是加入名為kCFRunLoopDefaultMode的model中皱坛,所以只有應(yīng)用程序在kCFRunLoopDefaultMode下编曼,Timer才會工作。
如果這個時候滑動了屏幕剩辟,那么應(yīng)用的mode就從名字為kCFRunLoopDefaultMode的mode中退出掐场,進(jìn)入到名稱為UITrackingRunLoopMode的model中。因為NSTimer沒有添加到名字為UITrackingRunLoopMode的item中贩猎,所以只要等待不再滑動回到kCFRunLoopDefaultMode的時候才再次開始工作熊户。
怎么解決呢?也就是把Timer加到了UITrackingRunLoopMode的Item吭服。怎么樣加到UITrackingRunLoopMode中呢敏弃?通過NSRunLoopCommonModes標(biāo)記之后,Runloop會把NSTimer加入到Runloop中的commonModeItems中噪馏。上面講過RunLoop 都會自動將 commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 標(biāo)記的所有Model里麦到。
RunLoop內(nèi)部邏輯
上面的介紹Source/Timer/Observer 數(shù)據(jù)結(jié)構(gòu)被統(tǒng)稱為 mode item绿饵,一個 item 可以被同時加入多個 mode。但一個 item 被重復(fù)加入同一個 mode 時是不會有效果的瓶颠。如果一個 mode 中一個 item 都沒有拟赊,則 RunLoop 會直接退出,不進(jìn)入循環(huán)粹淋。
根據(jù)上面講的數(shù)據(jù)結(jié)構(gòu)可以得出下面這張圖:
[圖片上傳失敗...(image-4539dd-1521365090788)]
NSRunloop暴露給外界的接口
Runloop執(zhí)行過程
RunLoopRun的啟動
首先總的來說邏輯如下圖所示:
下面提供了兩套代碼吸祟,簡化版與非簡化版,如果覺得非簡化版代碼太長可以直接看簡化版的代碼桃移,他們所表達(dá)的含義是一樣的屋匕。
簡化版代碼(來源于網(wǎng)上)
/// 用DefaultMode啟動
void CFRunLoopRun(void) {
CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
}
/// 用指定的Mode啟動,允許設(shè)置RunLoop超時時間
int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
/// RunLoop的實現(xiàn)
int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
/// 首先根據(jù)modeName找到對應(yīng)mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
/// 如果mode里沒有source/timer/observer, 直接返回借杰。
if (__CFRunLoopModeIsEmpty(currentMode)) return;
/// 1. 通知 Observers: RunLoop 即將進(jìn)入 loop过吻。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
/// 內(nèi)部函數(shù),進(jìn)入loop
__CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
Boolean sourceHandledThisLoop = NO;
int retVal = 0;
do {
/// 2. 通知 Observers: RunLoop 即將觸發(fā) Timer 回調(diào)蔗衡。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: RunLoop 即將觸發(fā) Source0 (非port) 回調(diào)纤虽。
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 4. RunLoop 觸發(fā) Source0 (非port) 回調(diào)。
sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
/// 執(zhí)行被加入的block
__CFRunLoopDoBlocks(runloop, currentMode);
/// 5. 如果有 Source1 (基于port) 處于 ready 狀態(tài)绞惦,直接處理這個 Source1 然后跳轉(zhuǎn)去處理消息逼纸。
if (__Source0DidDispatchPortLastTime) {
Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
if (hasMsg) goto handle_msg;
}
/// 通知 Observers: RunLoop 的線程即將進(jìn)入休眠(sleep)。
if (!sourceHandledThisLoop) {
__CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
}
/// 7. 調(diào)用 mach_msg 等待接受 mach_port 的消息济蝉。線程將進(jìn)入休眠, 直到被下面某一個事件喚醒杰刽。
/// ? 一個基于 port 的Source 的事件。
/// ? 一個 Timer 到時間了
/// ? RunLoop 自身的超時時間到了
/// ? 被其他什么調(diào)用者手動喚醒
__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 到時間了淑仆,觸發(fā)這個Timer的回調(diào)涝婉。
if (msg_is_timer) {
__CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
}
/// 9.2 如果有dispatch到main_queue的block,執(zhí)行block蔗怠。
else if (msg_is_dispatch) {
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
}
/// 9.3 如果一個 Source1 (基于port) 發(fā)出事件了墩弯,處理這個事件
else {
CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
if (sourceHandledThisLoop) {
mach_msg(reply, MACH_SEND_MSG, reply);
}
}
/// 執(zhí)行加入到Loop的block
__CFRunLoopDoBlocks(runloop, currentMode);
if (sourceHandledThisLoop && stopAfterHandle) {
/// 進(jìn)入loop時參數(shù)說處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout) {
/// 超出傳入?yún)?shù)標(biāo)記的超時時間了
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(runloop)) {
/// 被外部調(diào)用者強制停止了
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
/// source/timer/observer一個都沒有了
retVal = kCFRunLoopRunFinished;
}
/// 如果沒超時寞射,mode里沒空渔工,loop也沒被停止,那繼續(xù)loop桥温。
} while (retVal == 0);
}
/// 10. 通知 Observers: RunLoop 即將退出引矩。
__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
上面的過程是對剛才那種圖的具體說明。在整個循環(huán)中,通過observer向外界告知當(dāng)前runloop的狀態(tài)旺韭,事件觸發(fā)由source(source0氛谜,source1)及timer發(fā)起,并且通過之前設(shè)置的函數(shù)進(jìn)行回調(diào)處理区端,在處理各個回調(diào)的時候也觸發(fā)了block的處理值漫。
非簡化版代碼(于CF-855.17)
下面的源碼來至于CF-855.17中
非簡化版代碼非常長,要非常有耐心才能看下去织盼。
CFRunLoopRun(入口函數(shù))
//默認(rèn)運行runloop的kCFRunLoopDefaultMode
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
//默認(rèn)在kCFRunLoopDefaultMode下運行runloop
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
CFRunLoopRunInMode
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
CFRunLoopRunSpecific
/*
* 指定mode運行runloop
* @param rl 當(dāng)前運行的runloop
* @param modeName 需要運行的mode的name
* @param seconds runloop的超時時間
* @param returnAfterSourceHandled 是否處理完事件就返回
*/
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
__CFRunLoopLock(rl);
//根據(jù)modeName找到本次運行的mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//如果沒找到 || mode中沒有注冊任何事件杨何,則就此停止,不進(jìn)入循環(huán)
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);
//取上一次運行的mode
CFRunLoopModeRef previousMode = rl->_currentMode;
//如果本次mode和上次的mode一致
rl->_currentMode = currentMode;
//初始化一個result為kCFRunLoopRunFinished
int32_t result = kCFRunLoopRunFinished;
// 1.通知observer即將進(jìn)入runloop
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//10.通知observer已退出runloop
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
__CFRunLoopModeUnlock(currentMode);
__CFRunLoopPopPerRunData(rl, previousPerRun);
rl->_currentMode = previousMode;
__CFRunLoopUnlock(rl);
return result;
}
- 如果指定了一個不存在的mode來運行RunLoop沥邻,那么會失敗危虱,mode不會被創(chuàng)建,所以這里傳入的mode必須是存在的唐全。
- 如果指定了一個mode埃跷,但是這個mode中不包含任何modeItem,那么RunLoop也不會運行芦瘾,所以必須要傳入至少包含一個modeItem的mode捌蚊。
- 在進(jìn)入run loop之前通知observer集畅,狀態(tài)為kCFRunLoopEntry近弟。
- 在退出run loop之后通知observer,狀態(tài)為kCFRunLoopExit挺智。
__CFRunloopRun(核心5挥洹!)
__CFRunloopRun才是最為重要的一步赦颇,runloop啟動操作最終會執(zhí)行這個方法二鳄。上面提到的簡化版主要是對下面這個方法簡化。
/**
* 運行run loop
*
* @param rl 運行的RunLoop對象
* @param rlm 運行的mode
* @param seconds run loop超時時間
* @param stopAfterHandle true:run loop處理完事件就退出 false:一直運行直到超時或者被手動終止
* @param previousMode 上一次運行的mode
*
* @return 返回4種狀態(tài)
*/
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
//獲取系統(tǒng)啟動后的CPU運行時間媒怯,用于控制超時時間
uint64_t startTSR = mach_absolute_time();
//如果RunLoop或者mode是stop狀態(tài)订讼,則直接return,不進(jìn)入循環(huán)
if (__CFRunLoopIsStopped(rl)) {
__CFRunLoopUnsetStopped(rl);
return kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
return kCFRunLoopRunStopped;
}
//mach端口扇苞,在內(nèi)核中欺殿,消息在端口之間傳遞。 初始為0
mach_port_name_t dispatchPort = MACH_PORT_NULL;
//判斷是否為主線程
Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
//如果在主線程 && runloop是主線程的runloop && 該mode是commonMode鳖敷,則給mach端口賦值為主線程收發(fā)消息的端口
if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
#if USE_DISPATCH_SOURCE_FOR_TIMERS
mach_port_name_t modeQueuePort = MACH_PORT_NULL;
if (rlm->_queue) {
//mode賦值為dispatch端口_dispatch_runloop_root_queue_perform_4CF
modeQueuePort = _dispatch_runloop_root_queue_get_port_4CF(rlm->_queue);
if (!modeQueuePort) {
CRASH("Unable to get port for run loop mode queue (%d)", -1);
}
}
#endif
//GCD管理的定時器脖苏,用于實現(xiàn)runloop超時機制
dispatch_source_t timeout_timer = NULL;
struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
//立即超時
if (seconds <= 0.0) { // instant timeout
seconds = 0.0;
timeout_context->termTSR = 0ULL;
}
//seconds為超時時間,超時時執(zhí)行__CFRunLoopTimeout函數(shù)
else if (seconds <= TIMER_INTERVAL_LIMIT) {
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
dispatch_retain(timeout_timer);
timeout_context->ds = timeout_timer;
timeout_context->rl = (CFRunLoopRef)CFRetain(rl);
timeout_context->termTSR = startTSR + __CFTimeIntervalToTSR(seconds);
dispatch_set_context(timeout_timer, timeout_context); // source gets ownership of context
dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout);
dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel);
uint64_t ns_at = (uint64_t)((__CFTSRToTimeInterval(startTSR) + seconds) * 1000000000ULL);
dispatch_source_set_timer(timeout_timer, dispatch_time(1, ns_at), DISPATCH_TIME_FOREVER, 1000ULL);
dispatch_resume(timeout_timer);
}
//永不超時
else { // infinite timeout
seconds = 9999999999.0;
timeout_context->termTSR = UINT64_MAX;
}
//標(biāo)志位默認(rèn)為true
Boolean didDispatchPortLastTime = true;
//記錄最后runloop狀態(tài)定踱,用于return
int32_t retVal = 0;
do {
//初始化一個存放內(nèi)核消息的緩沖池
uint8_t msg_buffer[3 * 1024];
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *msg = NULL;
mach_port_t livePort = MACH_PORT_NULL;
#elif DEPLOYMENT_TARGET_WINDOWS
HANDLE livePort = NULL;
Boolean windowsMessageReceived = false;
#endif
//取所有需要監(jiān)聽的port
__CFPortSet waitSet = rlm->_portSet;
//設(shè)置RunLoop為可以被喚醒狀態(tài)
__CFRunLoopUnsetIgnoreWakeUps(rl);
//2.通知observer,即將觸發(fā)timer回調(diào),處理timer事件
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
//3.通知observer爪幻,即將觸發(fā)Source0回調(diào)
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
//執(zhí)行加入當(dāng)前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
//4.處理source0事件
//有事件處理返回true,沒有事件返回false
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
//執(zhí)行加入當(dāng)前runloop的block
__CFRunLoopDoBlocks(rl, rlm);
}
//如果沒有Sources0事件處理 并且 沒有超時恤浪,poll為false
//如果有Sources0事件處理 或者 超時,poll都為true
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//第一次do..whil循環(huán)不會走該分支肴楷,因為didDispatchPortLastTime初始化是true
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
//從緩沖區(qū)讀取消息
msg = (mach_msg_header_t *)msg_buffer;
//5.接收dispatchPort端口的消息资锰,(接收source1事件)
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0)) {
//如果接收到了消息的話,前往第9步開始處理msg
goto handle_msg;
}
#elif DEPLOYMENT_TARGET_WINDOWS
if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) {
goto handle_msg;
}
#endif
}
didDispatchPortLastTime = false;
//6.通知觀察者RunLoop即將進(jìn)入休眠
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
//設(shè)置RunLoop為休眠狀態(tài)
__CFRunLoopSetSleeping(rl);
// do not do any user callouts after this point (after notifying of sleeping)
// Must push the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced.
__CFPortSetInsert(dispatchPort, waitSet);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//這里有個內(nèi)循環(huán)阶祭,用于接收等待端口的消息
//進(jìn)入此循環(huán)后绷杜,線程進(jìn)入休眠,直到收到新消息才跳出該循環(huán)濒募,繼續(xù)執(zhí)行run loop
do {
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
//7.接收waitSet端口的消息
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
//收到消息之后鞭盟,livePort的值為msg->msgh_local_port,
if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
// Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
if (rlm->_timerFired) {
// Leave livePort as the queue port, and service timers below
rlm->_timerFired = false;
break;
} else {
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
}
} else {
// Go ahead and leave the inner loop.
break;
}
} while (1);
#else
if (kCFUseCollectableAllocator) {
objc_clear_stack(0);
memset(msg_buffer, 0, sizeof(msg_buffer));
}
msg = (mach_msg_header_t *)msg_buffer;
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY);
#endif
#elif DEPLOYMENT_TARGET_WINDOWS
// Here, use the app-supplied message queue mask. They will set this if they are interested in having this run loop receive windows messages.
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, poll ? 0 : TIMEOUT_INFINITY, rlm->_msgQMask, &livePort, &windowsMessageReceived);
#endif
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
// Must remove the local-to-this-activation ports in on every loop
// iteration, as this mode could be run re-entrantly and we don't
// want these ports to get serviced. Also, we don't want them left
// in there if this function returns.
__CFPortSetRemove(dispatchPort, waitSet);
__CFRunLoopSetIgnoreWakeUps(rl);
// user callouts now OK again
//取消runloop的休眠狀態(tài)
__CFRunLoopUnsetSleeping(rl);
//8.通知觀察者runloop被喚醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//9.處理收到的消息
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
#if DEPLOYMENT_TARGET_WINDOWS
if (windowsMessageReceived) {
// These Win32 APIs cause a callout, so make sure we're unlocked first and relocked after
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
if (rlm->_msgPump) {
rlm->_msgPump();
} else {
MSG msg;
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE | PM_NOYIELD)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
// To prevent starvation of sources other than the message queue, we check again to see if any other sources need to be serviced
// Use 0 for the mask so windows messages are ignored this time. Also use 0 for the timeout, because we're just checking to see if the things are signalled right now -- we will wait on them again later.
// NOTE: Ignore the dispatch source (it's not in the wait set anymore) and also don't run the observers here since we are polling.
__CFRunLoopSetSleeping(rl);
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
__CFRunLoopWaitForMultipleObjects(waitSet, NULL, 0, 0, &livePort, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
__CFRunLoopUnsetSleeping(rl);
// If we have a new live port then it will be handled below as normal
}
#endif
if (MACH_PORT_NULL == livePort) {
CFRUNLOOP_WAKEUP_FOR_NOTHING();
// handle nothing
//通過CFRunloopWake喚醒
} else if (livePort == rl->_wakeUpPort) {
CFRUNLOOP_WAKEUP_FOR_WAKEUP();
//什么都不干瑰剃,跳回2重新循環(huán)
// do nothing on Mac OS
#if DEPLOYMENT_TARGET_WINDOWS
// Always reset the wake up port, or risk spinning forever
ResetEvent(rl->_wakeUpPort);
#endif
}
#if USE_DISPATCH_SOURCE_FOR_TIMERS
//如果是定時器事件
else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
//9.1 處理timer事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer, because we apparently fired early
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
#if USE_MK_TIMER_TOO
//如果是定時器事件
else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
CFRUNLOOP_WAKEUP_FOR_TIMER();
// On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
// In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
//9.1處理timer事件
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
// Re-arm the next timer
__CFArmNextTimerInMode(rlm, rl);
}
}
#endif
//如果是dispatch到main queue的block
else if (livePort == dispatchPort) {
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
#if DEPLOYMENT_TARGET_WINDOWS
void *msg = 0;
#endif
//9.2執(zhí)行block
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
_CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
sourceHandledThisLoop = true;
didDispatchPortLastTime = true;
} else {
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
// 有source1事件待處理
if (rls) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
mach_msg_header_t *reply = NULL;
//9.2 處理source1事件
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
if (NULL != reply) {
(void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
}
#elif DEPLOYMENT_TARGET_WINDOWS
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
#endif
}
}
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
#endif
__CFRunLoopDoBlocks(rl, rlm);
if (sourceHandledThisLoop && stopAfterHandle) {
//進(jìn)入run loop時傳入的參數(shù)齿诉,處理完事件就返回
retVal = kCFRunLoopRunHandledSource;
}else if (timeout_context->termTSR < mach_absolute_time()) {
//run loop超時
retVal = kCFRunLoopRunTimedOut;
}else if (__CFRunLoopIsStopped(rl)) {
//run loop被手動終止
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
}else if (rlm->_stopped) {
//mode被終止
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
}else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
//mode中沒有要處理的事件
retVal = kCFRunLoopRunFinished;
}
//除了上面這幾種情況,都繼續(xù)循環(huán)
} while (0 == retVal);
if (timeout_timer) {
dispatch_source_cancel(timeout_timer);
dispatch_release(timeout_timer);
} else {
free(timeout_context);
}
return retVal;
}
- 給源代碼添加注釋是一件非常需要耐心的事情晌姚。
可以看到粤剧,實際上 RunLoop 就是這樣一個函數(shù),其內(nèi)部是一個 do-while 循環(huán)挥唠。當(dāng)你調(diào)用 CFRunLoopRun() 時抵恋,線程就會一直停留在這個循環(huán)里;直到超時或被手動停止宝磨,該函數(shù)才會返回弧关。同時RunLoop有很多個mode,但是RunLoop在run的時候必須只能指定其中一個mode唤锉,運行起來之后世囊,被指定的mode即為currentMode。
這里有個細(xì)節(jié)RunLoop 的超時時間就是使用 GCD 中的 dispatch_source_t來實現(xiàn)的對應(yīng)到上面的代碼可以看一看窿祥。
Runloop相關(guān)操作
CFRunLoop是基于pthread來管理株憾。iOS中不能直接創(chuàng)建Runloop,只能從系統(tǒng)中獲取CFRunLoopGetMain() 和 CFRunLoopGetCurrent()晒衩。
系統(tǒng)一共提供了如下幾種方式嗤瞎,區(qū)別在于面向的框架不一樣:
CFRunLoopRef CFRunLoopGetCurrent(void);//獲取當(dāng)前線程的RunLoop對象
CFRunLoopRef CFRunLoopGetMain(void);//獲取主線程的RunLoop對象
+(NSRunLoop *)currentRunLoop
+(NSRunLoop *)mainRunLoop
獲取當(dāng)前線程
//取當(dāng)前所在線程的RunLoop
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if (rl) return rl;
//傳入當(dāng)前線程
return _CFRunLoopGet0(pthread_self());
}
在CFRunLoopGetCurrent函數(shù)內(nèi)部調(diào)用了_CFRunLoopGet0(),傳入的參數(shù)是當(dāng)前線程(eturn _CFRunLoopGet0(pthread_self());
)浸遗。這里可以看出猫胁,CFRunLoopGetCurrent函數(shù)必須要在線程內(nèi)部調(diào)用,才能獲取當(dāng)前線程的RunLoop跛锌。也就是說子線程的RunLoop必須要在子線程內(nèi)部獲取弃秆。
CFRunLoopGetMain
//取主線程的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;
}
在CFRunLoopGetMain函數(shù)內(nèi)部也調(diào)用了_CFRunLoopGet0()届惋,傳入的參數(shù)是主線程〔ぷ可以看出脑豹,CFRunLoopGetMain()不管在主線程還是子線程中調(diào)用,都可以獲取到主線程的RunLoop衡查。
CFRunLoopGet0
獲取當(dāng)前的及主線程的runloop最終都是調(diào)用CFRunLoopGet0來實現(xiàn)的瘩欺。
//全局的Dictionary,key 是 pthread_t拌牲, value 是 CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
/// 訪問 loopsDic 時的鎖, 可以知道__CFRunLoops是線程不安全的
static CFSpinLock_t loopsLock = CFSpinLockInit;
// should only be called by Foundation
// t==0 is a synonym for "main thread" that always works
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
// 如果傳入線程為空俱饿,默認(rèn)則為主線程
if (pthread_equal(t, kNilPthreadT)) {
t = pthread_main_thread_np();
}
//加鎖訪問字典
__CFSpinLock(&loopsLock);
if (!__CFRunLoops) {
// 第一次進(jìn)入時,初始化全局__CFSpinUnlock字典塌忽,并先為主線程創(chuàng)建一個 RunLoop拍埠。
__CFSpinUnlock(&loopsLock);
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
//釋放臨時創(chuàng)建的變量
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
//解鎖
__CFSpinLock(&loopsLock);
}
//如果不是第一次,則直接從字典里面取
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFSpinUnlock(&loopsLock);
if (!loop) {
//如果取不到則創(chuàng)建一個新的CFRunLoopRef土居,然后存在全局的字典里面枣购,傳入的參數(shù)(線程指針)作為key
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);
}
//如果傳入的線程是當(dāng)前線程
if (pthread_equal(t, pthread_self())) {
_CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
// 注冊一個回調(diào),當(dāng)線程銷毀時擦耀,順便也銷毀其對應(yīng)的 RunLoop棉圈。
_CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
}
}
return loop;
}
通過上面代碼可以總結(jié)出:
- 線程和 RunLoop 之間是一一對應(yīng)的,其關(guān)系是保存在一個全局的 Dictionary 里眷蜓。
- 線程剛創(chuàng)建時并沒有 RunLoop(沒有加到對應(yīng)的runloop字典中)分瘾,如果你不主動獲取,那它一直都不會有账磺。
- RunLoop 的創(chuàng)建是發(fā)生在第一次獲取時芹敌。一般是獲取主線程的時候痊远。
- RunLoop 的銷毀是發(fā)生在線程結(jié)束時垮抗。
- 只能在一個線程的內(nèi)部獲取其 RunLoop(主線程除外),否則就這個Runloop就沒有注冊銷毀回調(diào)碧聪。這一點是根據(jù)
pthread_equal(t, pthread_self())
后面的代碼冒版,如果是當(dāng)前線程后面才會注冊銷毀回調(diào)。因為上面講過Runlopp暴露給外部的創(chuàng)建方式只有CFRunLoopGetMain() 和 CFRunLoopGetCurrent()兩種逞姿,所以這種情況不用考慮辞嗡。下面是CFRunloop.h的頭文件暴露接口,可以看到獲取方式只有兩種滞造。
Mode相關(guān)操作
在Core Foundation中续室,針對Mode的操作,蘋果只開放了如下API:
CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef mode)//向當(dāng)前RunLoop的common modes中添加一個mode谒养。
CFStringRef CFRunLoopCopyCurrentMode(CFRunLoopRef rl)//返回當(dāng)前運行的mode的name
CFArrayRef CFRunLoopCopyAllModes(CFRunLoopRef rl)//返回當(dāng)前RunLoop的所有mode
我們沒有辦法直接創(chuàng)建一個CFRunLoopMode對象挺狰,但是我們可以調(diào)用CFRunLoopAddCommonMode傳入一個字符串向RunLoop中添加Mode,傳入的字符串即為Mode的名字,Mode對象應(yīng)該是此時在RunLoop內(nèi)部創(chuàng)建的丰泊。特別注意只能通過CFRunLoopAddCommonMode薯定,是CommonMode。
CFRunLoopAddCommonMode
void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
CHECK_FOR_FORK();
if (__CFRunLoopIsDeallocating(rl)) return;
__CFRunLoopLock(rl);
//看rl中是否已經(jīng)有這個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 */
//__CFRunLoopAddItemsToCommonMode是一個方法话侄,里面會調(diào)用CFRunLoopAddSource/CFRunLoopAddObserver/CFRunLoopAddTimer
//__CFRunLoopFindMode(rl, modeName, true),CFRunLoopMode對象在這個時候被創(chuàng)建
CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
CFRelease(set);
}
} else {
}
__CFRunLoopUnlock(rl);
}
可以總結(jié)出如下幾點:
- modeName不能重復(fù)学赛,modeName是mode的唯一標(biāo)識符
- 添加commonMode會把commonModeItems數(shù)組中的所有item(source年堆,observe,timer,)同步到新添加的mode中盏浇。
CFRunLoopCopyCurrentMode/CFRunLoopCopyAllModes實現(xiàn)比較簡單嘀韧,直接返回對應(yīng)的model。
Source相關(guān)操作(ModeItem)
系統(tǒng)提供了如下幾個函數(shù)來操作Item
CF_EXPORT Boolean CFRunLoopContainsSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFRunLoopMode mode);
CF_EXPORT Boolean CFRunLoopContainsObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFRunLoopMode mode);
CF_EXPORT Boolean CFRunLoopContainsTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
CF_EXPORT void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFRunLoopMode mode);
可以看到總體來講提供了對item的判斷是否已經(jīng)包含item缠捌,添加锄贷,刪除的功能。
CFRunLoopAddSource
作用:將一個CFRunLoopAddSource對象添加到一個指定的Runloop Model中曼月。
//添加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);
//如果是kCFRunLoopCommonModes
if (modeName == kCFRunLoopCommonModes) {
//如果runloop的_commonModes存在谊却,則copy一個新的復(fù)制給set
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
//如果runl _commonModeItems為空
if (NULL == rl->_commonModeItems) {
//先初始化
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
//把傳入的CFRunLoopSourceRef加入_commonModeItems
CFSetAddValue(rl->_commonModeItems, rls);
//如果剛才set copy到的數(shù)組里有數(shù)據(jù)
if (NULL != set) {
CFTypeRef context[2] = {rl, rls};
/* add new item to all common-modes */
//則把set里的所有mode都執(zhí)行一遍__CFRunLoopAddItemToCommonModes函數(shù)
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
//以上分支的邏輯就是,如果你往kCFRunLoopCommonModes里面添加一個source哑芹,那么所有_commonModes里的mode都會添加這個source
} else {
//根據(jù)modeName查找mode
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true);
//如果_sources0不存在炎辨,則初始化_sources0,_sources0和_portToV1SourceMap
if (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中都不包含傳入的source
if (NULL != rlm && !CFSetContainsValue(rlm->_sources0, rls) && !CFSetContainsValue(rlm->_sources1, rls)) {
//如果version是0聪姿,則加到_sources0
if (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對應(yīng)起來
//可以理解為,source1可以通過內(nèi)核向其端口發(fā)送消息來主動喚醒runloop
CFDictionarySetValue(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 lock
rls->_context.version0.schedule(rls->_context.version0.info, rl, modeName); /* CALLOUT */
}
}
可以總結(jié)如下:
- source會被runloop持有末购。
- 如果modeName傳入kCFRunLoopCommonModes破喻,則該source會被保存到RunLoop的commonModeItems中,然后添加到common models每個mode下面盟榴,進(jìn)而被所有的common models監(jiān)控曹质。
- 如果modeName傳入的不是kCFRunLoopCommonModes,則會先查找該Mode擎场,如果沒有羽德,會創(chuàng)建一個。
- 同一個source在一個mode中只能被添加一次迅办。
- 只有在加到source1的時候才會把souce和一個mach_port_t對應(yīng)起來宅静,這個mach_port_t由source傳入。
CFRunLoopRemoveSource
remove操作和add操作的邏輯基本一致站欺,很容易理解
//移除source
void CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef rls, CFStringRef modeName) { /* DOES CALLOUT */
CHECK_FOR_FORK();
Boolean doVer0Callout = false, doRLSRelease = false;
__CFRunLoopLock(rl);
//如果是kCFRunLoopCommonModes姨夹,則從_commonModes的所有mode中移除該source
if (modeName == kCFRunLoopCommonModes) {
if (NULL != rl->_commonModeItems && CFSetContainsValue(rl->_commonModeItems, rls)) {
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
CFSetRemoveValue(rl->_commonModeItems, rls);
if (NULL != set) {
CFTypeRef context[2] = {rl, rls};
/* remove new item from all common-modes */
CFSetApplyFunction(set, (__CFRunLoopRemoveItemFromCommonModes), (void *)context);
CFRelease(set);
}
} else {
}
} else {
//根據(jù)modeName查找mode究驴,如果不存在,返回NULL
CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, false);
if (NULL != rlm && ((NULL != rlm->_sources0 && CFSetContainsValue(rlm->_sources0, rls)) || (NULL != rlm->_sources1 && CFSetContainsValue(rlm->_sources1, rls)))) {
CFRetain(rls);
//根據(jù)source版本做對應(yīng)的remove操作
if (1 == rls->_context.version0.version) {
__CFPort src_port = rls->_context.version1.getPort(rls->_context.version1.info);
if (CFPORT_NULL != src_port) {
CFDictionaryRemoveValue(rlm->_portToV1SourceMap, (const void *)(uintptr_t)src_port);
__CFPortSetRemove(src_port, rlm->_portSet);
}
}
CFSetRemoveValue(rlm->_sources0, rls);
CFSetRemoveValue(rlm->_sources1, rls);
__CFRunLoopSourceLock(rls);
if (NULL != rls->_runLoops) {
CFBagRemoveValue(rls->_runLoops, rl);
}
__CFRunLoopSourceUnlock(rls);
if (0 == rls->_context.version0.version) {
if (NULL != rls->_context.version0.cancel) {
doVer0Callout = true;
}
}
doRLSRelease = 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.cancel(rls->_context.version0.info, rl, modeName); /* CALLOUT */
}
if (doRLSRelease) CFRelease(rls);
}
CFRunLoopAddItemToCommonModes
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
CFStringRef modeName = (CFStringRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);//取出runloop
CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);//取出添加到item(可能是source匀伏、observer洒忧、timer)
//根據(jù)類型添加到對應(yīng)的model中。
if (CFGetTypeID(item) == CFRunLoopSourceGetTypeID()) {
CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopObserverGetTypeID()) {
CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
} else if (CFGetTypeID(item) == CFRunLoopTimerGetTypeID()) {
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
}
}
Observer/Timer的相關(guān)操作
添加observer和timer的內(nèi)部邏輯和添加source大體類似够颠。
區(qū)別在于observer和timer只能被添加到一個RunLoop的一個或者多個mode中熙侍,比如一個timer被添加到主線程的RunLoop中,則不能再把該timer添加到子線程的RunLoop履磨,而source沒有這個限制蛉抓,不管是哪個RunLoop,只要mode中沒有剃诅,就可以添加巷送。
上面的記錄可以從CFRunLoopSource結(jié)構(gòu)體可以明確的知道。CFRunLoopSource中有保存RunLoop對象的數(shù)組矛辕,而CFRunLoopObserver和CFRunLoopTimer只有單個RunLoop對象笑跛。
小結(jié)
以上內(nèi)容是對runloop從源碼角度的理解過程。由于代碼比較多聊品,看起來也費事飞蹂,可以直接選擇重點內(nèi)容看。下面的內(nèi)容主要來至于深入理解RunLoop這篇文章翻屈。畢竟這篇文章在國內(nèi)來講陈哑,是研究runloop文章中,國內(nèi)寫得非常有參考價值的文章伸眶。
Runloop在系統(tǒng)中的應(yīng)用(下面內(nèi)容大部分來源于深入理解RunLoop)
首先把之前的runloop執(zhí)行過程中的函數(shù)使用長函數(shù)名改一下惊窖,這樣便于在實際調(diào)試中便于分析。因為在真實debug時不會出現(xiàn)上面的函數(shù)名厘贼,而是通過長函數(shù)名代替界酒,在源碼中也能看到之前的函數(shù)名和這里的長函數(shù)名是同一個函數(shù)。
{
/// 1. 通知Observers涂臣,即將進(jìn)入RunLoop
/// 此處有Observer會創(chuàng)建AutoreleasePool: _objc_autoreleasePoolPush();
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
do {
/// 2. 通知 Observers: 即將觸發(fā) Timer 回調(diào)盾计。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
/// 3. 通知 Observers: 即將觸發(fā) Source (非基于port的,Source0) 回調(diào)。
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 4. 觸發(fā) Source0 (非基于port的) 回調(diào)赁遗。
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
/// 6. 通知Observers,即將進(jìn)入休眠
/// 此處有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喚醒的岩四,回調(diào)Timer
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
/// 9. 如果是被dispatch喚醒的,執(zhí)行所有調(diào)用 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);
}
這里舉個例子材鹦,如下debug信息:
對應(yīng)到上面的過程就是:觸發(fā) Source0 (非基于port的) 回調(diào)。
下圖是對主線程的runloop的中在kCFRunLoopDefaultMode模式下所有的observer的日志耕姊。
Block
在最開始介紹CFRunloop的時候就簡單提了一下其中關(guān)于block的兩個字段blocks_head桶唐,blocks_tail。并且也提到在runloop周期中會對此調(diào)用__CFRunLoopDoBlocks來執(zhí)行加入到這個runloop的block茉兰。下面從源碼來說明一下block如何與runloop結(jié)合的尤泽。
先來看看最基本的block_item 數(shù)據(jù)結(jié)構(gòu),特別注意這里保存了runloop的model规脸,決定了block是否應(yīng)該執(zhí)行坯约。
struct _block_item {
struct _block_item *_next;
CFTypeRef _mode; // CFString or CFSet
void (^_block)(void);
};
在執(zhí)行block的時候會傳入
/**
執(zhí)行block
@param rl runloop
@param rlm 當(dāng)前的model
@return 是否執(zhí)行
*/
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
//如果頭結(jié)點沒有、或者model不存在則強制返回莫鸭,什么也不做
if (!rl->_blocks_head) return false;
if (!rlm || !rlm->_name) return false;
Boolean did = false;//記錄其中一個block結(jié)點是否被執(zhí)行過
//取出頭尾結(jié)點闹丐,并且將當(dāng)前runloop保存的頭尾節(jié)點置位NULL
struct _block_item *head = rl->_blocks_head;
struct _block_item *tail = rl->_blocks_tail;
rl->_blocks_head = NULL;
rl->_blocks_tail = NULL;
//取出被標(biāo)記為common的所有mode、及當(dāng)前model的name
CFSetRef commonModes = rl->_commonModes;
CFStringRef curMode = rlm->_name;
__CFRunLoopModeUnlock(rlm);
__CFRunLoopUnlock(rl);
//定義兩個臨時變量被因,用于對保存block鏈表的遍歷
struct _block_item *prev = NULL;
struct _block_item *item = head;//記錄頭指針卿拴,從頭部開始遍歷
//開始遍歷block鏈表
while (item) {
struct _block_item *curr = item;
item = item->_next;
Boolean doit = false;//表示是否應(yīng)該執(zhí)行這個block,注意和前面的did區(qū)分開
//從blockitem結(jié)構(gòu)體就知道,其中的_mode只能是CFString 或者CFSet
//如果block結(jié)點保存的model是CFString類型
if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
//是否執(zhí)行block只需要滿足下面三個條件中的一個
//1. blockitem 中保存的model是當(dāng)前的model
//2. blockitem 中保存的model是標(biāo)記為kCFRunLoopCommonModes的model
//3. 當(dāng)前model保存在commonModes數(shù)組
doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
} else {
//如果block結(jié)點保存的model是CFSet類型梨与,步驟和上面一樣巍棱,等于換成了包含。
doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
}
//如果不執(zhí)行block,則直接移動當(dāng)前結(jié)點蛋欣,進(jìn)行下一個blockitem的判斷
if (!doit) prev = curr;
if (doit) {
//如果執(zhí)行block,則先移動結(jié)點航徙。
if (prev) prev->_next = item;
if (curr == head) head = item;
if (curr == tail) tail = prev;
void (^block)(void) = curr->_block;
CFRelease(curr->_mode);
free(curr);
if (doit) {
//最終在這里執(zhí)行block,__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__的函數(shù)原型就是調(diào)用block
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
did = true;
}
Block_release(block); // do this before relocking to prevent deadlocks where some yahoo wants to run the run loop reentrantly from their dealloc
}
}
__CFRunLoopLock(rl);
__CFRunLoopModeLock(rlm);
//重建循環(huán)鏈表
if (head) {
tail->_next = rl->_blocks_head;
rl->_blocks_head = head;
if (!rl->_blocks_tail) rl->_blocks_tail = tail;
}
return did;
}
通過上面分析可以知道:
- block其實在runloop中通過循環(huán)鏈表保存的
- 如果block可以加入到多個model下面陷虎,但是執(zhí)行block只有在加入的那個model下才能之后到踏,或者加入modle用common標(biāo)記
- 每次調(diào)用__CFRunLoopDoBlocks,會把加入的block遍歷執(zhí)行尚猿,然后重置循環(huán)鏈表窝稿。
AutoreleasePool
App啟動后,蘋果在主線程 RunLoop 里注冊了兩個 Observer凿掂,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()伴榔。
第一個 Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop),其回調(diào)內(nèi)會調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動釋放池庄萎。其 order 是-2147483647踪少,優(yōu)先級最高,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前糠涛。
第二個 Observer 監(jiān)視了兩個事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池援奢;Exit(即將退出Loop) 時調(diào)用 _objc_autoreleasePoolPop() 來釋放自動釋放池。這個 Observer 的 order 是 2147483647忍捡,優(yōu)先級最低集漾,保證其釋放池子發(fā)生在其他所有回調(diào)之后切黔。
在主線程執(zhí)行的代碼,通常是寫在諸如事件回調(diào)具篇、Timer回調(diào)內(nèi)的纬霞。這些回調(diào)會被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著,所以不會出現(xiàn)內(nèi)存泄漏驱显,開發(fā)者也不必顯示創(chuàng)建 Pool 了诗芜。
關(guān)于Autorelease后面需要一篇源碼分析來說明問題。
手勢識別
上面可以看到第二個observe就是_UIGestureRecognizerUpdateObserver秒紧,關(guān)于手勢識別的绢陌。
當(dāng)上面的 _UIApplicationHandleEventQueue() 識別了一個手勢時,其首先會調(diào)用 Cancel 將當(dāng)前的 touchesBegin/Move/End 系列回調(diào)打斷熔恢。隨后系統(tǒng)將對應(yīng)的 UIGestureRecognizer 標(biāo)記為待處理脐湾。
蘋果注冊了一個 Observer 監(jiān)測 BeforeWaiting (Loop即將進(jìn)入休眠) 事件,這個Observer的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver()叙淌,其內(nèi)部會獲取所有剛被標(biāo)記為待處理的 GestureRecognizer秤掌,并執(zhí)行GestureRecognizer的回調(diào)。
當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時鹰霍,這個回調(diào)都會進(jìn)行相應(yīng)處理闻鉴。
界面更新
上面可以看到第三和四個observe分別是_beforeCACommitHandler與_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv,是關(guān)于動畫及界面更新的茂洒。
當(dāng)在操作 UI 時孟岛,比如改變了 Frame、更新了 UIView/CALayer 的層次時督勺,或者手動調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后渠羞,這個 UIView/CALayer 就被標(biāo)記為待處理,并被提交到一個全局的容器去智哀。
蘋果注冊了一個 Observer 監(jiān)聽 BeforeWaiting(即將進(jìn)入休眠) 和 Exit (即將退出Loop) 事件次询,回調(diào)去執(zhí)行一個很長的函數(shù):
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。這個函數(shù)里會遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實際的繪制和調(diào)整瓷叫,并更新 UI 界面屯吊。
定時器
上面截圖中還有個timer
NSTimer 其實就是 CFRunLoopTimerRef,他們之間是 toll-free bridged 的摹菠。一個 NSTimer 注冊到 RunLoop 后盒卸,RunLoop 會為其重復(fù)的時間點注冊好事件。例如 10:00, 10:10, 10:20 這幾個時間點辨嗽。RunLoop為了節(jié)省資源世落,并不會在非常準(zhǔn)確的時間點回調(diào)這個Timer。Timer 有個屬性叫做 Tolerance (寬容度)糟需,標(biāo)示了當(dāng)時間點到后屉佳,容許有多少最大誤差。
如果某個時間點被錯過了洲押,例如執(zhí)行了一個很長的任務(wù)武花,則那個時間點的回調(diào)也會跳過去,不會延后執(zhí)行杈帐。就比如等公交体箕,如果 10:10 時我忙著玩手機錯過了那個點的公交,那我只能等 10:20 這一趟了挑童。
PerformSelecter
當(dāng)調(diào)用 NSObject 的 performSelecter:afterDelay: 來實現(xiàn)延遲執(zhí)行累铅,實際上其內(nèi)部會創(chuàng)建一個 Timer 并添加到當(dāng)前線程的 RunLoop 中。所以如果當(dāng)前線程沒有 RunLoop站叼,則這個方法會失效娃兽。
當(dāng)調(diào)用 performSelector:onThread: 時,實際上其會創(chuàng)建一個 Timer 加到對應(yīng)的線程去尽楔,同樣的投储,如果對應(yīng)線程沒有 RunLoop 該方法也會失效。
GCD
在看runloop執(zhí)行過程的源碼中阔馋,可以知道RunLoop 的超時時間就是使用 GCD 中的 dispatch_source_t來實現(xiàn)的玛荞。
當(dāng)調(diào)用 dispatch_async(dispatch_get_main_queue(), block) 時,libDispatch 會向主線程的 RunLoop 發(fā)送消息呕寝,RunLoop會被喚醒勋眯,并從消息中取得這個 block,并在回調(diào) CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里執(zhí)行這個 block下梢。但這個邏輯僅限于 dispatch 到主線程客蹋,dispatch 到其他線程仍然是由 libDispatch 處理的。
網(wǎng)絡(luò)請求
iOS 中怔球,關(guān)于網(wǎng)絡(luò)請求的接口自下至上有如下幾層:
CFSocket
CFNetwork ->ASIHttpRequest
NSURLConnection ->AFNetworking
NSURLSession ->AFNetworking2, Alamofire
CFSocket 是最底層的接口嚼酝,只負(fù)責(zé) socket 通信。
? CFNetwork 是基于 CFSocket 等接口的上層封裝竟坛,ASIHttpRequest 工作于這一層闽巩。
? NSURLConnection 是基于 CFNetwork 的更高層的封裝,提供面向?qū)ο蟮慕涌诘L溃珹FNetworking 工作于這一層涎跨。
? NSURLSession 是 iOS7 中新增的接口,表面上是和 NSURLConnection 并列的崭歧,但底層仍然用到了 NSURLConnection 的部分功能 (比如 com.apple.NSURLConnectionLoader 線程)隅很,AFNetworking2 和 Alamofire 工作于這一層。
工作原理
通常使用 NSURLConnection 時率碾,你會傳入一個 Delegate叔营,當(dāng)調(diào)用了 [connection start] 后屋彪,這個 Delegate 就會不停收到事件回調(diào)。實際上绒尊,start 這個函數(shù)的內(nèi)部會會獲取 CurrentRunLoop畜挥,然后在其中的 DefaultMode 添加了4個 Source0 (即需要手動觸發(fā)的Source)。CFMultiplexerSource 是負(fù)責(zé)各種 Delegate 回調(diào)的婴谱,CFHTTPCookieStorage 是處理各種 Cookie 的蟹但。
當(dāng)開始網(wǎng)絡(luò)傳輸時,我們可以看到 NSURLConnection 創(chuàng)建了兩個新線程:com.apple.NSURLConnectionLoader 和 com.apple.CFSocket.private谭羔。其中 CFSocket 線程是處理底層 socket 連接的华糖。NSURLConnectionLoader 這個線程內(nèi)部會使用 RunLoop 來接收底層 socket 的事件,并通過之前添加的 Source0 通知到上層的 Delegate瘟裸。
NSURLConnectionLoader 中的 RunLoop 通過一些基于 mach port 的 Source 接收來自底層 CFSocket 的通知客叉。當(dāng)收到通知后,其會在合適的時機向 CFMultiplexerSource 等 Source0 發(fā)送通知景描,同時喚醒 Delegate 線程的 RunLoop 來讓其處理這些通知十办。CFMultiplexerSource 會在 Delegate 線程的 RunLoop 對 Delegate 執(zhí)行實際的回調(diào)。
Runloop在平時開發(fā)中的應(yīng)用(深入理解RunLoop)
AFNetworking
為了線程背祝活向族。
代碼:
- (void)start {
[self.lock lock];
if ([self isCancelled]) {
[self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
} else if ([self isReady]) {
self.state = AFOperationExecutingState;
[self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
}
[self.lock unlock];
}
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
});
return _networkRequestThread;
}
+ (void)networkRequestThreadEntryPoint:(id)__unused object {
@autoreleasepool {
[[NSThread currentThread] setName:@"AFNetworking"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
上面函數(shù)的調(diào)用關(guān)系由上到下調(diào)用。
AF希望能在后臺線程接收 Delegate 回調(diào)棠绘。為此 AFNetworking 單獨創(chuàng)建了一個線程件相,并在這個線程中啟動了一個 RunLoop。過程在networkRequestThreadEntryPoint中氧苍,因為RunLoop 啟動前內(nèi)部必須要有至少一個 Timer/Observer/Source夜矗,所以 AFNetworking 在 [runLoop run] 之前先創(chuàng)建了一個新的 NSMachPort 添加進(jìn)去了。通常情況下让虐,調(diào)用者需要持有這個 NSMachPort (mach_port) 并在外部線程通過這個 port 發(fā)送消息到 loop 內(nèi)紊撕;但此處添加 port 只是為了讓 RunLoop 不至于退出,并沒有用于實際的發(fā)送消息赡突。
當(dāng)需要這個后臺線程執(zhí)行任務(wù)時对扶,AFNetworking 通過調(diào)用 [NSObject performSelector:onThread:..] 將這個任務(wù)扔到了后臺線程的 RunLoop 中,具體內(nèi)容對應(yīng)start方法惭缰。
UITableView+FDTemplateLayoutCell
利用runloop的空閑狀態(tài)計算高度達(dá)到預(yù)緩存的功能浪南。具體分析見這里優(yōu)化UITableViewCell高度計算的那些事
AsyncDisplayKit
ASDK 仿照 QuartzCore/UIKit 框架的模式,實現(xiàn)了一套類似的界面更新的機制:即在主線程的 RunLoop 中添加一個 Observer漱受,監(jiān)聽了 kCFRunLoopBeforeWaiting 和 kCFRunLoopExit 事件络凿,在收到回調(diào)時,遍歷所有之前放入隊列的待處理的任務(wù),然后一一執(zhí)行絮记。
可以直接看源碼進(jìn)行分析AsyncDisplayKit摔踱,但是現(xiàn)在更名為Texture