RunLoop

一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:

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事件。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容