RunLoop

一般來講黄鳍,一個線程一次只能執(zhí)行一個任務关串,執(zhí)行完成后線程就會退出。如果我們需要一個機制踩寇,讓線程能隨時處理事件但并不退出啄清,通常的代碼邏輯是這樣的:

function loop(){
initialize();
do{ var message = get_next_message(); process_message(message); }while(message != quit)
}

這種模型通常被稱作Event Loop。Event Loop在很多系統(tǒng)和框架里都有實現(xiàn)俺孙,比如Node.js的事件處理辣卒,比如Windows程序的消息循環(huán),再比如Windows程序的消息循環(huán)睛榄,再比如OSX/iOS里的RunLoop荣茫。實現(xiàn)這種模型的關鍵點在于:如何管理事件/消息,如何讓線程在沒有處理消息時休眠以避免資源占用场靴、在有消息到來時立刻被喚醒啡莉。

RunLoop實際上就是一個對象,這個對象管理了其需要處理的事件和消息旨剥,并提供一個入口函數(shù)來執(zhí)行Event Loop的邏輯票罐。線程執(zhí)行了這個函數(shù)后,就會一直處于這個函數(shù)內部“接受消息->等待->處理”的循環(huán)中泞边,直到這個循環(huán)結束(比如傳入quit的消息),函數(shù)返回疗杉。

OSX/iOS系統(tǒng)中,提供了兩個這樣的對象:NSRunLoop 和 CFRunLoopRef.

CFRunLoopRef

CFRunLoopRef提供了純C函數(shù)的API梢什,所有這些API都是線程安全的朝聋。
蘋果提供了兩個自動獲取的函數(shù):CFRunLoopGetMain()和CFRunLoopGetCurrent().
它們分別是:
CFRunLoopRef CFRunLoopGetMain(void){ CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; if(!__main) __main = _CFRunLoopGet0(pthread_main_thread_up()); return __main; }

CFRunLoopRef CFRunLoopGetCurrent(void){ CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if(rl) return rl; return _CFRunLoopGet0(pthread_self()); }

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; }

RunLoop的創(chuàng)建是發(fā)生在第一次獲取的時候嗡午,RunLoop的銷毀是發(fā)生在線程結束的時候。每個線程和它的RunLoop是一一對應的荔睹,其關系保存在一個全局的Dic中。(上述代碼中在獲取RunLoop時僻他,會判斷全局Dic是否存在宵距,如果不存在會先創(chuàng)建,同時會創(chuàng)建mainLoop吨拗,往下就會將mainLoop寫進Dic并對應主線程满哪,最后會CFRelease掉劝篷,不知道創(chuàng)建一遍mainLoop到底是有什么作用。)

CoreFoundation里面關于RunLoop有5個類:
CFRunLoopRef , CFRunLoopModeRef , CFRunLoopSourceRef ,
CFRunLoopTimeRef , CFRunLoopObserverRef

CFRunLoopRef是生成runloop對象的類像鸡,如下代碼:
CFRunLoopRef runLoop = CFRunLoopGetCurrent();

CFRunLoopModeRef代表RunLoop的運行模式
(1)一個RunLoop包含若干個Mode峡蟋,每個Mode又包含若干個Source/Timer/Observer
(2)每次RunLoop啟動時,只能指定其中一個Mode蕊蝗,這個Mode被稱作CurrentMode
(3)如果需要切換Mode,只能退出Loop夸楣,再重新指定一個Mode進入子漩,這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響紧显。

KCFRunLoopDefaultMode:App的默認Mode缕棵,通常主線程是在這個Mode下運行
UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView追蹤觸摸滑動招驴,保證界面滑動時不受其他影響
UIInitializationRunLoopMode:在剛啟動App時進入的第一個Mode别厘,啟動完成后就不再使用
GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內部Mode,通常用不到
kCFRunLoopCommonModes:這是一個占位用的Mode,不是一種真正的Mode
CFRunLoopMode的結構大致如下:
struct __CFRunLoopMode{ CFStringRef _name; CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; }

CFRunLoopTimeRef

CFRunLoopTimeRef 是基于時間的觸發(fā)器渴肉。其包含一個時間長度和一個回調(函數(shù)指針)折柠。當其加入到RunLoop時,RunLoop會注冊對應的時間點前塔,當時間點到時承冰,RunLoop會被喚醒以執(zhí)行那個回調。如下代碼:
void timer_event(CFRunLoopTimerRef timer __unused, void *info) { printf("timer_event--timer_event--timer_event\n"); }

void add _timer() { CFRunLoopTimerRef timer; CFRunLoopTimerContext timer_context; bzero(&timer_context, sizeof(timer_context)); timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 1, 0, 0, timer_event, &timer_context); CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes); CFRunLoopRun(); }
或者
-(void)addTimer { CFRunLoopRef runLoopRef = CFRunLoopGetCurrent(); NSTimer * timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(countEvent) userInfo:nil repeats:YES]; CFRunLoopTimerRef loopTimerRef = (__bridge CFRunLoopTimerRef)timer; CFRunLoopAddTimer(runLoopRef, loopTimerRef, kCFRunLoopDefaultMode); CFRunLoopRun(); }
或者GCD封裝的計時器
-(void)addGcdTimer { dispatch_source_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0,0)); dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 3.0*NSEC_PER_SEC); uint64_t interval = 1.0 * NSEC_PER_SEC; dispatch_source_set_timer(timer, startTime, interval, 0*NSEC_PER_SEC); dispatch_source_set_event_handler(timer, ^{ NSLog(@"test------%@",[NSThread currentThread]); }); dispatch_resume(timer); }

CFRunLoopObserverRef

CFRunLoopObserverRef是觀察者寂屏,每個Observer都包含了一個回調(函數(shù)指針)娜搂,當RunLoop的狀態(tài)發(fā)生變化時,觀察者就能通過回調接受到這個變化考廉⌒可以觀測的時間點有以下幾個:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity){ kCFRunLoopEntry =(1UL << 0), //即將進入Loop kCFRunLoopBeforeTimers =(1UL << 1), //即將處理Timer kCFRunLoopBeforeSources =(1UL << 2), //即將處理Source kCFRunLoopBeforeWaiting =(1UL << 5), //即將進入休眠 kCFRunLoopAfterWaiting =(1UL << 6), //剛從休眠中喚醒 kCFRunLoopExit =(1UL << 7), //即將退出Loop }
代碼如下:
static void _perform(void *info __unused) { printf("hello\n"); }

static void _timer(CFRunLoopTimerRef timer __unused, void *info) { printf("timer_event-timer_event-timer_event\n"); CFRunLoopSourceSignal(info); }

static void _observer(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) { switch(activity) { case kCFRunLoopEntry: NSLog(@"即將進入Loop"); break; case kCFRunLoopBeforeTimers: NSLog(@"即將處理Timer"); break; case kCFRunLoopBeforeSources: NSLog(@"即將處理Source"); break; case kCFRunLoopBeforeWaiting: NSLog(@"即將進入休眠"); break; case kCFRunLoopAfterWaiting: NSLog(@"剛從休眠中喚醒"); break; case kCFRunLoopExit: NSLog(@"即將退出Loop"); break; default: break; } }
void observer_event(){ CFRunLoopSourceRef source; CFRunLoopSourceContext source_context; CFRunLoopTimerRef timer; CFRunLoopTimerContext timer_context; CFRunLoopObserverRef observerRef; CFRunLoopObserverContext observer_context; bzero(&source_context, sizeof(source_context)); source_context.perform = _perform; source = CFRunLoopSourceCreate(NULL, 0, &source_context); CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); bzero(&timer_context, sizeof(timer_context)); bzero(&observer_context, sizeof(observer_context)); observerRef = CFRunLoopObserverCreate(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, _observer, &observer_context); CFRunLoopAddObserver(CFRunLoopGetCurrent(), observerRef, kCFRunLoopCommonModes); timer_context.info = source; timer = CFRunLoopTimerCreate(NULL, CFAbsoluteTimeGetCurrent(), 1, 0, 0, _timer, &timer_context); CFRunLoopAddTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes); CFRunLoopRun(); }

CFRunLoopSourceRef

CFRunLoopSourceRef是事件產生的地方啄刹。Source有兩個版本:Source0和Source1誓军。
Source0只包含了一個回調(函數(shù)指針),它并不能主動觸發(fā)事件昵时。使用時,你需要先調用CFRunLoopSourceSignal(source),將這個Source標記為待處理瓜挽,然后手動調用CFRunLoopWakeUp(runloop)來喚醒RunLoop,讓其處理這個事件俄占。
Source1包含了一個mach_port和一個回調(函數(shù)指針),被用于通過內核和其他線程相護發(fā)送消息渤弛。這種Source能主動喚醒RunLoop的線程甚带。
Source0的代碼如下:
void source_event(void *info) { printf(source_evnet\n); }
void click(void *info) { CFRunLoopSourceSignal(info); }
void source_event() { CFRunLoopSourceRef sourceRef; CFRunLoopSourceContext source_context; bzero(&source_context, sizeof(source_context)); source_context.perform = perform_event; source = CFRunLoopSourceCreate(NULL, 0, &source_context); CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes); CFRunLoopRun(); }
通過上述的click事件后將Source標記為待處理就可以處理source事件。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末晴氨,一起剝皮案震驚了整個濱河市碉输,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌枝哄,老刑警劉巖阻荒,帶你破解...
    沈念sama閱讀 218,386評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異瘪贱,居然都是意外死亡辆毡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評論 3 394
  • 文/潘曉璐 我一進店門球昨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來眨攘,“玉大人,你說我怎么就攤上這事鲫售∏橹瘢” “怎么了?”我有些...
    開封第一講書人閱讀 164,704評論 0 353
  • 文/不壞的土叔 我叫張陵雏蛮,是天一觀的道長。 經(jīng)常有香客問我挑秉,道長,這世上最難降的妖魔是什么立哑? 我笑而不...
    開封第一講書人閱讀 58,702評論 1 294
  • 正文 為了忘掉前任阱冶,我火速辦了婚禮,結果婚禮上至耻,老公的妹妹穿的比我還像新娘镊叁。我一直安慰自己,他們只是感情好晦譬,可當我...
    茶點故事閱讀 67,716評論 6 392
  • 文/花漫 我一把揭開白布敛腌。 她就那樣靜靜地躺著,像睡著了一般像樊。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颤霎,一...
    開封第一講書人閱讀 51,573評論 1 305
  • 那天涂滴,我揣著相機與錄音,去河邊找鬼缔杉。 笑死搁料,一個胖子當著我的面吹牛进苍,可吹牛的內容都是我干的鸭叙。 我是一名探鬼主播拣宏,決...
    沈念sama閱讀 40,314評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼宋下!你這毒婦竟也來了辑莫?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,230評論 0 276
  • 序言:老撾萬榮一對情侶失蹤枝笨,失蹤者是張志新(化名)和其女友劉穎揭蜒,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屉更,經(jīng)...
    沈念sama閱讀 45,680評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡瑰谜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,873評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了隐轩。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片砚哗。...
    茶點故事閱讀 39,991評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖提鸟,靈堂內的尸體忽然破棺而出仅淑,到底是詐尸還是另有隱情称勋,我是刑警寧澤,帶...
    沈念sama閱讀 35,706評論 5 346
  • 正文 年R本政府宣布赡鲜,位于F島的核電站,受9級特大地震影響嘲更,放射性物質發(fā)生泄漏揩瞪。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,329評論 3 330
  • 文/蒙蒙 一宠哄、第九天 我趴在偏房一處隱蔽的房頂上張望嗤攻。 院中可真熱鬧,春花似錦妇菱、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至涌萤,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間透揣,已是汗流浹背川抡。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留崖堤,地道東北人。 一個月前我還...
    沈念sama閱讀 48,158評論 3 370
  • 正文 我出身青樓楔脯,卻偏偏與公主長得像胯甩,于是被迫代替她去往敵國和親堪嫂。 傳聞我的和親對象是個殘疾皇子木柬,可洞房花燭夜當晚...
    茶點故事閱讀 44,941評論 2 355

推薦閱讀更多精彩內容