一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:
function loop(){
initialize();
do{ var message = get_next_message(); process_message(message); }while(message != quit)
}
这种模型通常被称作Event Loop。Event Loop在很多系统和框架里都有实现,比如Node.js的事件处理,比如Windows程序的消息循环,再比如Windows程序的消息循环,再比如OSX/iOS里的RunLoop。实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。
RunLoop实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供一个入口函数来执行Event Loop的逻辑。线程执行了这个函数后,就会一直处于这个函数内部“接受消息->等待->处理”的循环中,直到这个循环结束(比如传入quit的消息),函数返回。
OSX/iOS系统中,提供了两个这样的对象:NSRunLoop 和 CFRunLoopRef.
CFRunLoopRef
CFRunLoopRef提供了纯C函数的API,所有这些API都是线程安全的。
苹果提供了两个自动获取的函数: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的创建是发生在第一次获取的时候,RunLoop的销毁是发生在线程结束的时候。每个线程和它的RunLoop是一一对应的,其关系保存在一个全局的Dic中。(上述代码中在获取RunLoop时,会判断全局Dic是否存在,如果不存在会先创建,同时会创建mainLoop,往下就会将mainLoop写进Dic并对应主线程,最后会CFRelease掉,不知道创建一遍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:接受系统事件的内部Mode,通常用不到
kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode
CFRunLoopMode的结构大致如下:
struct __CFRunLoopMode{ CFStringRef _name; CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers; }
CFRunLoopTimeRef
CFRunLoopTimeRef 是基于时间的触发器。其包含一个时间长度和一个回调(函数指针)。当其加入到RunLoop时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。如下代码:
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都包含了一个回调(函数指针),当RunLoop的状态发生变化时,观察者就能通过回调接受到这个变化。可以观测的时间点有以下几个:
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只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用CFRunLoopSourceSignal(source),将这个Source标记为待处理,然后手动调用CFRunLoopWakeUp(runloop)来唤醒RunLoop,让其处理这个事件。
Source1包含了一个mach_port和一个回调(函数指针),被用于通过内核和其他线程相护发送消息。这种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事件。