RunLoop原始碼分析、基本使用場景和常見問題
YY大神的連結
goole ASDK的Runloop超流暢UI思路
Runloop
從字面上看
- 迴圈執行 內部核心其實就是一個do while迴圈
- 跑圈 就和這個小夥子一樣
BOOL running = YES; do{ // 處理各種事件執行各種任務 }while(running)
- 基本作用
- 保持程式的持續執行
- 處理App中的各種事件(比如觸控事件、定時器事件、Selector事件)
- 節省CPU資源,提高程式效能,做事的時候跑起來,不做的時候休眠
Runloop與執行緒
- 每條執行緒都有唯一的一個與之對應的runloop物件
- 主執行緒的runloop自動建立,子執行緒的runloop需要手動建立,不建立就沒有
- Runloop在第一次獲取時建立,線上程結束時銷燬
獲取runloop物件
Fundation
- [NSRunLoop currentRunLoop] // 獲取當前執行緒的
- [NSRunLoop mainRunLoop]// 獲取主執行緒
core fundation
- CFRunLoopGetCurrent();// 獲取當前執行緒
- CFRunLoopGetMain(); // 獲取主執行緒
原始碼分析系列,當我們呼叫上面的程式碼時,來看看原始碼原始碼下載地址
1.入口
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());
}
2.看到_CFRunLoopGet0
這個函式沒,這就是第二部,內部實現
// 全域性字典,key是pthread_t value是CFRunLoopRef
static CFMutableDictionaryRef __CFRunLoops = NULL;
// 訪問__CFRunLoops時的鎖
static CFLock_t loopsLock = CFLockInit;
// 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();
}
__CFLock(&loopsLock);
// 第一次進來全域性字典裡面沒有任何東西
if (!__CFRunLoops) {
__CFUnlock(&loopsLock);
// 初始化全域性字典
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 根據主執行緒建立主執行緒RunLoop
CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
// 加入到全域性字典 key main_thread value mainLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
// 和全域性__CFRunLoops關聯上,然後把臨時建立的殺死
if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
// 如果全域性字典存在,根據執行緒取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) {
// 根據執行緒key把剛建立的關聯上
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
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;
}
大致建立邏輯就是上面註釋,你不需要顯示alloc,你只需要獲取就行了,內部已經有建立的邏輯,沒錯,RunLoop物件就搞定了,下面來看看物件到底是什麼結構
RunLoop相關類
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
CFRunLoopModeRef代表RunLoop的執行模式
- 一個 RunLoop 包含若干個 Mode,每個Mode又包含若干個Source/Timer/Observer
每次RunLoop啟動時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode
如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入
- 這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
系統註冊了五個Mode(可以打斷點看呼叫棧)
1.kCFRunLoopDefaultMode:App的預設Mode,通常主執行緒實在這個Mode
2.UITrackingRunLoopMode: 頁面跟蹤Mode,用於滾動追蹤觸控滑動,保證頁面滑動式不受其他Mode影響
NSTimer預設是加到DefaultMode裡面的,當滾動式切換到這個模式,所以之前的Timer事件不再呼叫
3.UIInitializationRunLoopMode:在剛啟動App時第一次進入就是這個Mode,完成之後不再用
4.GSEventReveiveRunLoopMode:系統事件的內部Mode,通常用不到
5.CFRunLoopCommonModes:這個是一個佔位用的Mode,不是一個真正的Mode,
用來標示切換不同Mode時是否加有這個欄位的Mode還是能繼續接受事件
CFRunLoopSourceRef是事件源(輸入源)
理論分Source:
- Port-Based Sources 基於Mach port核心/其他執行緒事件源
- Custom Input Sources 自定義基本不用
- Cocoa Perform Selector Sources [Self PerformSele…]
按呼叫棧分Source:
- Source0:非基於Port
- Source1:基於Port,通過核心和其他執行緒通訊,接受,分發系統事件
CFRunLoopTimerRef)
這個東西就是一個定時器,通過設定特定的時間間隔來給叫醒RunLoop處理事件
CFRunLoopObserverRef
CFRunLoopObserverRef是觀察者,能夠監聽RunLoop的狀態改變
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),// 即將進入RunLoop 1
kCFRunLoopBeforeTimers = (1UL << 1),// 即將處理Timer事件 2
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理事件源 4
kCFRunLoopBeforeWaiting = (1UL << 5),// 即將進入休眠 32
kCFRunLoopAfterWaiting = (1UL << 6),// 即將進入從休眠中醒來 64
kCFRunLoopExit = (1UL << 7),// 退出 128
kCFRunLoopAllActivities = 0x0FFFFFFFU // 上述所有模式監聽列舉
};
上述提到的Source/Timer/OBserver就是一個ModeItem,如果一個Mode中一個Item都沒有,RunLoop會自動退出,這就是後面要提到的常駐執行緒建立方式,先看看RunLoop和Mode兩者的結構,然後再舉例子對上面的行為進行分析
struct __CFRunLoop {
CFRuntimeBase _base;
CFMutableSetRef _commonModes; // 名字Default/Tracking
CFMutableSetRef _commonModeItems;// Timer/Source/Observer
CFRunLoopModeRef _currentMode;// 當前Mode
CFMutableSetRef _modes;//一個RunLoop下有多個Mode
};
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; // 用來存放到RunLoop中_commonModes/_modes的欄位
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;// 非port事件
CFMutableSetRef _sources1;// port事件
CFMutableArrayRef _observers;// 觀察者
CFMutableArrayRef _timers;// timer
......
}
個人理解下,RunLoop結構體中有個modes用來存放所有可切換的Mode,例如Default/Tracking等,currentMode就指定當前在運作的Mode,CommandModes就是存放著無論你切換哪個Mode,你都需要同步_commonModeItems下所有事件的ModeName,你可以把Mode看做一個個的屬於你自己的任務,而commentMode看做每個人都要做的公共任務,當你有公共任務的時候也就是棧頂有Commentmode的時候,你做自己的子線任務的時候,同時需要做棧頂的公共任務
例項1Timer
// 方法1
// on the current run loop in the default mode
// 建立的時候是加到NSDefaultRunLoopMode中的
// [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 方法2
// 其實和方法一是一樣的,只是展開來寫罷了
NSTimer *timer = [NSTimer timerWithTimeInterval:2.3 target:self selector:@selector(run) userInfo:nil repeats:YES];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 為了在滾動的時候和不滾動的時候都進行呼叫NSTimer
// 只需要把Timer加到當前RunLoop中的CommonModes Set容器裡面去就好了,無論切換什麼模式,都會同步該Timer
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
預設情況下我們建立的Timer只會在Default狀態下執行,當我們滾動的時候Timer就會不呼叫事件,原因就是RunLoop中它預設加到了NSDefaultMode中,當滾動的時候切換到NSTrackingMode的時候,NSTimer自然就不執行了,這是因為RunLoop只會在一個Mode下執行,當切換Mode的時候需要重啟Loop,而且事件不會同步,除非你把事件加到棧頂的CommonModes裡面去,這樣就可以在當前RunLoop下的所有模式下共享執行了
例項2Observer,可以在事件處理前進行一些攔截
// typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
// kCFRunLoopEntry = (1UL << 0),// 即將進入RunLoop 1
// kCFRunLoopBeforeTimers = (1UL << 1),// 即將處理Timer事件 2
// kCFRunLoopBeforeSources = (1UL << 2), // 即將處理事件源 4
// kCFRunLoopBeforeWaiting = (1UL << 5),// 即將進入休眠 32
// kCFRunLoopAfterWaiting = (1UL << 6),// 即將進入從休眠中醒來 64
// kCFRunLoopExit = (1UL << 7),// 退出 128
// kCFRunLoopAllActivities = 0x0FFFFFFFU // 上述所有模式監聽列舉
// };
CFRunLoopObserverRef observe = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
if (activity == kCFRunLoopExit) {
NSLog(@"即將退出runloop%lu",activity);
}
else if (activity == kCFRunLoopBeforeWaiting)
{
NSLog(@"即將進入休眠%lu",activity);
}
else if (activity == kCFRunLoopAfterWaiting)
{
NSLog(@"剛從休眠中喚醒%lu",activity);
}else if (activity == kCFRunLoopBeforeSources)
{
NSLog(@"即將處理 Source%lu",activity);
}else if (activity == kCFRunLoopBeforeTimers)
{
NSLog(@"即將處理 Timer%lu",activity);
}
else if (activity == kCFRunLoopEntry)
{
NSLog(@"即將進入Loop%lu",activity);
}
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observe, kCFRunLoopDefaultMode);
/*
CF的記憶體管理(Core Foundation)
1.凡是帶有Create、Copy、Retain等字眼的函式,建立出來的物件,都需要在最後做一次release
* 比如CFRunLoopObserverCreate
2.release函式:CFRelease(物件);
*/
CFRelease(observe);
例項3 AFN2x和YYKit下 的常駐執行緒
// YYKit常駐執行緒
/// Network thread entry point.
+ (void)_networkThreadMain:(id)object {
// 常規做法最外層也用autorelease包起來
@autoreleasepool {
[[NSThread currentThread] setName:@"com.ibireme.yykit.webimage.request"];
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
// 給建立的loop新增任意一個item
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
/// Global image request network thread, used by NSURLConnection delegate.
+ (NSThread *)_networkThread {
static NSThread *thread = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
thread = [[NSThread alloc] initWithTarget:self selector:@selector(_networkThreadMain:) object:nil];
if ([thread respondsToSelector:@selector(setQualityOfService:)]) {
thread.qualityOfService = NSQualityOfServiceBackground;
}
[thread start];
});
return thread;
}
這裡的做法是建立 一個RunLoop,但是如果沒有新增任何Obserer/Timer/Source,Runloop預設你是會退出的,要進入迴圈必須加一個item
RunLoop整體邏輯
這個圖的前提需要注意的是,需要檢查ModeItem是否為空,是的話直接退出Loop
原始碼分析
//1.直接呼叫Run方法
void CFRunLoopRun(void) { /* DOES CALLOUT */
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
// 通過指定Mode呼叫Run方法
SInt32 CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
}
// 2.啟動
SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */
//3. 根據RunloopModeName找到Mode
CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
//4. 如果Mode為空裡面沒有任何的item直接返回
if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
return kCFRunLoopRunFinished;
}
//5. 如果不為空 先通知Observer:即將進入Loop kCFRunLoopEntry = (1UL << 0),// 即將進入RunLoop 1
if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
//6. 進入核心函式進行跑圈loop
result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
//end: kCFRunLoopExit = (1UL << 7),// 退出 128
if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
return result;
}
// 核心跑圈程式碼
/* rl, rlm are locked on entrance and exit */
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
uint64_t startTSR = mach_absolute_time();
__CFPortSet waitSet = rlm->_portSet;
__CFRunLoopUnsetIgnoreWakeUps(rl);
// 8.通知Observer
// kCFRunLoopBeforeTimers = (1UL << 1),// 即將處理Timer事件 2
// kCFRunLoopBeforeSources = (1UL << 2), // 即將處理事件源 4
if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
// 任務加入Block處理
__CFRunLoopDoBlocks(rl, rlm);
//9. 處理Source0事件 執行事件加入block
Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
if (sourceHandledThisLoop) {
__CFRunLoopDoBlocks(rl, rlm);
}
Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
//10. 如果有source1進來,基於port的訊息進來 處理Source1 goto語句跳轉到下面
if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
msg = (mach_msg_header_t *)msg_buffer;
if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
// 跳轉
goto handle_msg;
}
}
didDispatchPortLastTime = false;
//11. 通知ObserverseRunloop即將進入休眠kCFRunLoopBeforeWaiting
if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
#if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI
#if USE_DISPATCH_SOURCE_FOR_TIMERS
do {
//12. 呼叫mach方法等待訊息的接受,執行緒進入休眠,當port傳來source1的時候,當Timer時間到了,當Loop超時了
// 當被顯示喚醒的時候
__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
} while (1);
//13. 通知Runloop執行緒被剛剛喚醒
if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
//14. 處理訊息
handle_msg:;
__CFRunLoopSetIgnoreWakeUps(rl);
//15. 如果Timer到了 觸發Timer回撥
CFRUNLOOP_WAKEUP_FOR_TIMER();
if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
__CFArmNextTimerInMode(rlm, rl);
}
}
//16. 如果是由dispatch到main_queue的block,執行block
CFRUNLOOP_WAKEUP_FOR_DISPATCH();
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
//17. 如果一個source1發出事件,處理source1
CFRUNLOOP_WAKEUP_FOR_SOURCE();
// Despite the name, this works for windows handles as well
CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop;
__CFRunLoopDoBlocks(rl, rlm);
//18.處理是否繼續迴圈
if (sourceHandledThisLoop && stopAfterHandle) {
// 進入loop時引數說處理完事件就返回。
retVal = kCFRunLoopRunHandledSource;
} else if (timeout_context->termTSR < mach_absolute_time()) {
// 超時
retVal = kCFRunLoopRunTimedOut;
} else if (__CFRunLoopIsStopped(rl)) {
// 外部強制停止
__CFRunLoopUnsetStopped(rl);
retVal = kCFRunLoopRunStopped;
} else if (rlm->_stopped) {
rlm->_stopped = false;
retVal = kCFRunLoopRunStopped;
} else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
// source/timer/observer為空一個都沒了
retVal = kCFRunLoopRunFinished;
}
} while (0 == retVal);// 還是0,繼續跑
return retVal;
}
關於Runloop的一些問題
什麼是RunLoop?
內部就是一個do - While迴圈,在這個迴圈內部不斷處理各種事件
(Source、Timer、Observer)
一個執行緒對應一個RunLoop,主執行緒RunLoop預設已經啟動,子執行緒的RunLoop得手動啟動
RunLoop只能選擇一個模式,如果當前模式中沒有Source、Timer、Obeserver直接突出你在開發中怎麼使用RunLoop?
1.AF2x.和YYKit常駐執行緒
在子執行緒中開啟定時器
子執行緒中長期監控一些行為(掃描網路、沙盒、語音監控)2.可以控制定時器在特定模式下執行
3.可以讓某些事件在特定模式下執行(Performance…inmodes(mode引數))
4.可以新增Observer監聽RunLoop的狀態,比如監聽點選事件處理前處理某些事情- 自動釋放池什麼時候釋放
在RunLoop睡眠之前釋放(kCFRunLoopBeforeWaiting)
總結
4.什麼是Runloop
Runloop他的本質就是一個do,while迴圈
(1)保持程式的持續執行,當有事做時做事,
(2)負責監聽處理App中的各種事件(比如觸控事件、定時器事件、Selector事件)
(3)節省CPU資源,提高程式效能,做事的時候跑起來,不做的時候休眠。
每個執行緒都有一個Run Loop,主執行緒的Run Loop會在App執行時自動執行,子執行緒中需要手動獲取執行,建立是發生在第一次獲取時,RunLoop 的銷燬是發生線上程結束時。
一個 RunLoop 包含若干個 Mode,每個 Mode 又包含若干個 Source/Timer/Observer。每次呼叫 RunLoop 的主函式時,只能指定其中一個 Mode,這個Mode被稱作 CurrentMode。如果需要切換 Mode,只能退出 Loop,再重新指定一個 Mode 進入。這樣做主要是為了分隔開不同組的 Source/Timer/Observer,讓其互不影響。當建立的loop,沒有任何上面的屬性,就會直接退出。
Run的時候必須制定mode,那麼這裡有個知識點是CommonMode,不能指定該Mode進行執行,但是可以把事件標記到該屬性,那麼無論切換到哪個Mode都會執行
Apple應用
1.AutoreleasePool
App啟動後,蘋果在主執行緒 RunLoop 裡註冊了兩個 Observer,第一個 Observer 監視的事件是 Entry(即將進入Loop),執行方法去建立pool,優先順序最高,第二個是BeforeWaiting(準備進入休眠) 時呼叫pop and push,先清除舊pool,然後建立新pool,或者再退出的時候釋放自動釋放池子,優先順序最低
2.啟動時註冊,處理事件,nstimer,UI,渲染
3.網路請求框架下的執行緒通訊
開發者應用:
1.常駐執行緒 處理網路請求的回撥
2.Common在不同模式下也執行NSTimer
3.新增Observe屬性的觀察,在即將進入休眠的ASDK的原理,以下就是AS框架的Runloop應用,這裡把排版,繪製等操作放到非同步做,然後儲存起來
在kCFRunLoopBeforeWaiting和kCFRunLoopExit兩個activity的回撥中將之前非同步完成的工作同步到主執行緒中去。
ASDK goole的UI框架原理
一個自稱用cell實現Runloop應用的人
__unsafe_unretained __typeof__(self) weakSelf = self;
void (^handlerBlock) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) = ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
[weakSelf processQueue];
};
_runLoopObserver = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, handlerBlock);
CFRunLoopAddObserver(_runLoop, _runLoopObserver, kCFRunLoopCommonModes);
結構:
struct __CFRunLoopMode {
CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
CFMutableSetRef _sources0; // Set
CFMutableSetRef _sources1; // Set
CFMutableArrayRef _observers; // Array
CFMutableArrayRef _timers; // Array
...
};
struct __CFRunLoop {
CFMutableSetRef _commonModes; // Set
CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
CFRunLoopModeRef _currentMode; // Current Runloop Mode
CFMutableSetRef _modes; // Set
};
相關文章
- Java Shutdown Hook 場景使用和原始碼分析JavaHook原始碼
- fishhook使用場景&原始碼分析Hook原始碼
- JUC之CountDownLatch的原始碼和使用場景分析CountDownLatch原始碼
- redis快取常見問題場景總結Redis快取
- MQ 常見的使用場景MQ
- SpaceVim的基本安裝和常見問題
- JasperReport報表生成工具的基本使用和常見問題
- Netty的原始碼分析和業務場景Netty原始碼
- sonar常見問題分析
- JVM之調優及常見場景分析JVM
- Redis 的 5 個常見使用場景Redis
- redis常見的幾種使用場景Redis
- Redis常見的16個使用場景Redis
- MySQL死鎖系列-常見加鎖場景分析MySql
- 如何分析Sonar常見問題?
- SDWebImage的基本用法及常見問題Web
- 資料中心代理的常見使用場景
- 使用代理IP的三個常見場景
- Redis常見應用場景Redis
- nginx實現常見場景Nginx
- 單例模式常見場景單例模式
- v-model 使用場景和原始碼學習原始碼
- 高併發場景下的快取有哪些常見的問題?快取
- 重拾RunLoop之原始碼分析1OOP原始碼
- mysqldump常見使用場景及引數參考MySql
- Redis 常見 7 種使用場景 (PHP 實戰)RedisPHP
- MySQL複製效能優化和常見問題分析MySql優化
- 使用兔展教程和常見製作問題
- 使用IDEA模擬git命令使用的常見場景IdeaGit
- Sentinel基本使用與原始碼分析原始碼
- MVC常遇見的幾個場景程式碼分享MVC
- Chaosblade 常見場景演練
- Redis 五種常見使用場景下 PHP 實戰RedisPHP
- 訊息佇列常見問題分析佇列
- MyCAT的常見問題分析和解決
- Jedis介紹及常見問題分析
- 雲his門診業務模組常見問題分析和門診業務使用流程
- 面試百問:Redis 常見的故障以及發生場景面試Redis