一般來講黄鳍,一個線程一次只能執(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事件。