NSHYJ

个人站

write the code, change the world.


runloop梳理总结

写在开头

> 印象之初,runloop支撑着其他一些功能,到底如何起作用,起什么作用的则不太清楚
> 接触之初,大概在定时器功能(NSTimer)
> 以后散散碎碎地阅读过一些blog,大概地了解了一些runloop的内在,然而并没有系统的整理过
> so,这次希望能把runloop相关系统地梳理总结

我的疑惑

  • 是什么?
  • 运行原理?
  • 何时何处用到?

一个例子

这是一段定时器的code example:

// 将timer默认添加到NSRunLoopCommonModes
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test:) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; 
// 将timer默认添加到NSDefaultRunLoopMode
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(fireTimer:) userInfo:nil repeats:YES]; 

基础

概念

a run loop,简单地说,就是一个循环。当没有事件时,Runloop 会进入休眠状态,有事件发生时, Runloop会去找对应的 Handler 处理事件。

APPLE官方给出的解释:
The NSRunLoop class declares the programmatic interface to objects that manage input sources. An NSRunLoop object processes input for sources such as mouse and keyboard events from the window system, NSPort objects, and NSConnection objects. An NSRunLoop object also processes NSTimer events.

Run loops are part of the fundamental infrastructure associated with threads. A run loop is an event processing loop that you use to schedule work and coordinate the receipt of incoming events. The purpose of a run loop is to keep your thread busy when there is work to do and put your thread to sleep when there is none.

作用

  • 使程序一直运行并接收用户的输入
  • 决定程序在何时处理哪些事件
  • 调用解耦(Message Queue)
  • 节省CPU时间(当程序启动后,什么都没有执行的话,就不用让CPU来消耗资源来执行,直接进入睡眠状态)

常用的一些方法

  
@interface NSRunLoop : NSObject

+ (NSRunLoop *)currentRunLoop;
+ (NSRunLoop *)mainRunLoop NS_AVAILABLE(10_5, 2_0);

- (CFRunLoopRef)getCFRunLoop CF_RETURNS_NOT_RETAINED;

- (void)addTimer:(NSTimer *)timer forMode:(NSString *)mode;

- (void)addPort:(NSPort *)aPort forMode:(NSString *)mode;
- (void)removePort:(NSPort *)aPort forMode:(NSString *)mode;

@end

@interface NSRunLoop (NSRunLoopConveniences)

- (void)run; 
- (void)runUntilDate:(NSDate *)limitDate;
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;

@end

/**************** 	Delayed perform	 ******************/

@interface NSObject (NSDelayedPerforming)

- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray<NSString *> *)modes;
- (void)performSelector:(SEL)aSelector withObject:(nullable id)anArgument afterDelay:(NSTimeInterval)delay;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(nullable id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;

@end

@interface NSRunLoop (NSOrderedPerform)

- (void)performSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg order:(NSUInteger)order modes:(NSArray<NSString *> *)modes;
- (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(nullable id)arg;
- (void)cancelPerformSelectorsWithTarget:(id)target;

@end  

//运行 NSRunLoop,运行模式为默认的NSDefaultRunLoopMode模式,没有超时限制
- (void)run;
//运行 NSRunLoop: 参数为运行模式、时间期限,返回值为YES表示是处理事件后返回的,NO表示是超时或者停止运行导致返回的。[Update]: 感谢网友olo的提醒:返回值只有在RunLoop没有运行的情况下才返回NO。比如:1)没有添加输入源和定时器源 2)mode为NSRunLoopCommonModes 或UITrackingRunLoopMode 等“非法”参数。如果超时的话返回YES,即使limitDate的初始值小于当前的Date,RunLoop也会执行一次然后马上返回YES。
- (BOOL)runMode:(NSString *)mode beforeDate:(NSDate *)limitDate;  
//运行 NSRunLoop: 参数为运时间期限,运行模式为默认的NSDefaultRunLoopMode模式
- (void)runUntilDate:(NSDate *)limitDate;

IMPORTANT
建议是使用第二个接口来运行,因为它能够设置Run Loop的运行参数最多,而且最重要的是可以使用CFRunLoopStop(runLoopRef)来停止Run Loop的运行,而第一个和第三个接口无法使用CFRunLoopStop(runLoopRef)来停止Run Loop的运行。

深入

剖析

RunLoop的内部过程可以用这样的伪代码来展现:

func run(runloop)
{
    do {
        runloop.wait()
        message = runloop.queue.pop()
        dispatch(message)
    } while(true)
}

Apple官方文档给出的运行图:

在这个循环里,会处理两种类别的事件源。一个来自于Input sources,一个来自于Timer sources.

  • Input sources:
    Input sources deliver asynchronous events, usually messages from another thread or from a different application. The input sources deliver asynchronous events to the corresponding handlers and cause the runUntilDate: method (called on the thread’s associated NSRunLoop object) to exit.
    • Port-Based Sources
      Cocoa and Core Foundation provide built-in support for creating port-based input sources using port-related objects and functions.
    • Custom Input Sources
      To create a custom input source, you must use the functions associated with the CFRunLoopSourceRef opaque type in Core Foundation.
    • Cocoa Perform Selector Sources
      In addition to port-based sources, Cocoa defines a custom input source that allows you to perform a selector on any thread.
  • Timer sources:
    Timer sources deliver synchronous events, occurring at a scheduled time or repeating interval. Timer sources deliver events to their handler routines but do not cause the run loop to exit.

###底层构成

the NSRunLoop class底层实现于Core FoundationCFRunLoopRef.
RunLoop相关的类:

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

runloop中source,observer,timer,mode之间的关系

ext:
在启动RunLoop之前,必须添加监听的输入源事件或者定时源事件,否则调用[runloop run]会直接返回,而不会进入循环让线程长驻。如果没有添加任何输入源事件或Timer事件,线程会一直在无限循环空转中,会一直占用CPU时间片,没有实现资源的合理分配。没有while循环且没有添加任何输入源或Timer的线程,线程会直接完成,被系统回收。

常见的runloop mode

  • NSDefaultRunLoopMode:(这是最常用的mode,几乎包含所有输入源,除NSConnection外)
    The default mode is the one used for most operations. Most of the time, you should use this mode to start your run loop and configure your input sources.
  • NSRunLoopCommonModes:(这是一个伪mode,其实是一个run loop mode的合集,默认包含default modes,modal modes,event Tracking modes)
    This is a configurable group of commonly used modes. For Cocoa applications, this set includes the default, modal, and event tracking modes by default. Core Foundation includes just the default mode initially. You can add custom modes to the set using the CFRunLoopAddCommonMode function.
  • UITrackingRunLoopMode:(手指拖动UITableView时处于这种模式)
    in drag loop or other user interface tracking loops in this mode, processing in this mode will limit the input event. For example, when the finger UITableView drag will in this mode.
  • NSConnectionReplyMode:(处理NSConnection对象相关事件,系统内部使用)
  • UIInitializationRunLoopMode:(在刚启动App时进入的第一个Mode,启动完成之后就不再使用)
  • GSEventReceiveRunLoopMode:(接收系统时间的内部 Mode,通常用不到)

ext:
在主线程启动一个计时器Timer,然后拖动UITableView或者UIScrollView,计时器不执行。这是因为,为了更好的用户体验,在主线程中Event tracking模式的优先级最高。在用户拖动控件时,主线程的Run Loop是运行在Event tracking Mode下,而创建的Timer是默认关联为Default Mode,因此系统不会立即执行Default Mode下接收的事件。

CFRunLoopSourceRef & CFRunLoopTimerRef & CFRunLoopObserverRef

  • CFRunLoopSourceRef
    There are two categories of sources.
    • source0:(主要是基于应用触发的事件源,如触摸滑动)(Custom Input Sources & Cocoa Perform Selector Sources)
      Version 0 sources, so named because the version field of their context structure is 0, are managed manually by the application. The run loop source for CFSocket is currently implemented as a version 0 source.
    • source1:(主要是系统内部基于port的事件源)(Port- Based Sources)
      Version 1 sources are managed by the run loop and kernel. These sources use Mach ports to signal when the sources are ready to fire. A source is automatically signaled by the kernel when a message arrives on the source’s Mach port. The run loop sources for CFMachPort and CFMessagePort are currently implemented as version 1 sources.
  • CFRunLoopTimerRef
    A CFRunLoopTimer object represents a specialized run loop source that fires at a preset time in the future.

    A timer is not a real-time mechanism; it fires only when one of the run loop modes to which the timer has been added is running and able to check if the timer’s firing time has passed. If a timer’s firing time occurs while the run loop is in a mode that is not monitoring the timer or during a long callout, the timer does not fire until the next time the run loop checks the timer.

    CFRunLoopTimer is “toll-free bridged” with its Cocoa Foundation counterpart, NSTimer. This means that the Core Foundation type is interchangeable in function or method calls with the bridged Foundation object. Therefore, in a method where you see an NSTimer * parameter, you can pass in a CFRunLoopTimerRef, and in a function where you see a CFRunLoopTimerRef parameter, you can pass in an NSTimer instance.

  • CFRunLoopObserverRef
    A CFRunLoopObserver provides a general means to receive callbacks at different points within a running run loop.

observer可以监听到的不同的点:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
	kCFRunLoopEntry = (1UL << 0),          //即将进入Runloop 
	kCFRunLoopBeforeTimers = (1UL << 1),   //即将处理NSTimer 
	kCFRunLoopBeforeSources = (1UL << 2),  //即将处理Sources 
	kCFRunLoopBeforeWaiting = (1UL << 5),  //即将进入休眠 
	kCFRunLoopAfterWaiting = (1UL << 6),   //刚从休眠中唤醒 
	kCFRunLoopExit = (1UL << 7),           //即将退出runloop 
    kCFRunLoopAllActivities = 0x0FFFFFFFU //所有状态改变
};

RunLoop处理事件的顺序

运行流程图:

###Run Loop Internals runloop底层地运行机制~

CFRunLoopRun简化后的主要逻辑:

在某个线程(有runloop run)的系统调用堆栈中会发现以CFRUNLOOP_IS_CALLING_OUT为开头的堆栈信息。
这里一共有6种,定义在CFRunLoop.c。

static void __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__();
static void __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__();
static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__();

上述函数分析:

  • CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
    向外部报告 RunLoop 当前状态的更改,框架中很多机制都由 RunLoopObserver 触发,如CAAnimation

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
    dispatch调用、block回调

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
    延迟的perform, 延迟dispatch调用

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
    处理App内部事件、App自己负责管理(触发),如UIEvent、CFSocket

  • CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
    由RunLoop和内核管理,Mach port驱动,如CFMachPort、CFMessagePort

  • CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE GCD~

CFRunLoopGetMain & CFRunLoopGetCurrent

源码中关于CFRunLoopGetMainCFRunLoopGetCurrent的实现~

//获取主线程runloop
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;
}
//获取当前线程runloop
CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
static CFMutableDictionaryRef __CFRunLoops = NULL;
static CFSpinLock_t loopsLock = CFSpinLockInit;
// 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();
    }
    __CFSpinLock(&loopsLock);
    //如果run loop Dic是否存在,若不存在,则初始化全局Dic,并为main thread创建RunLoop。
    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);
    //判断线程t是否存在runloop
    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是一一对应的。
RunLoop可以理解为延迟加载,子线程的RunLoop可以调用currentRunLoop,先从字典里面根据子线程取,如果没有就会去创建并与子线程绑定,保存到字典当中。

何时何处用到

The only time you need to run a run loop explicitly is when you create secondary threads for your application.

For secondary threads, you need to decide whether a run loop is necessary, and if it is, configure and start it yourself. You do not need to start a thread’s run loop in all cases. For example, if you use a thread to perform some long-running and predetermined task, you can probably avoid starting the run loop. Run loops are intended for situations where you want more interactivity with the thread. For example, you need to start a run loop if you plan to do any of the following:

  • Use ports or custom input sources to communicate with other threads.
  • Use timers on the thread.
  • Use any of the performSelector… methods in a Cocoa application.
  • Keep the thread around to perform periodic tasks.

如何使用Run Loop

下面代码简单地配置run loop:

* 添加一个observer来监听run loop的所有activities  
* 创建timer并将它添加到run loop  
* 实现功能...  
- (void)explore {
    //runloop~
    [NSThread detachNewThreadSelector:@selector(exploreRunLoop) toTarget:self withObject:nil];
}


- (void)exploreRunLoop {
    //...
    NSRunLoop *myRunLoop = [NSRunLoop currentRunLoop];
    
    // Create a run loop observer and attach it to the run loop.
    CFRunLoopObserverContext context = {0,(__bridge void *)(self),NULL,NULL,NULL};
    CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities, YES, 0, &myRunLoopObserver, &context);
    if (observer) {
        CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];
        CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);
    }
    
    // Create and schedule the timer.
    [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(fireTimer:) userInfo:nil repeats:YES];
    
    NSInteger loopCount = 10;
    do {
        // Run the run loop 10 times to let the timer fire.
        [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:1]];
        loopCount--;
    } while (loopCount);

}

void myRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info) {
    switch (activity) {
            //The entrance of the run loop, before entering the event processing loop.
            //This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode
        case kCFRunLoopEntry:
            NSLog(@"run loop entry");
            break;
            //Inside the event processing loop before any timers are processed
        case kCFRunLoopBeforeTimers:
            NSLog(@"run loop before timers");
            break;
            //Inside the event processing loop before any sources are processed
        case kCFRunLoopBeforeSources:
            NSLog(@"run loop before sources");
            break;
            //Inside the event processing loop before the run loop sleeps, waiting for a source or timer to fire.
            //This activity does not occur if CFRunLoopRunInMode is called with a timeout of 0 seconds.
            //It also does not occur in a particular iteration of the event processing loop if a version 0 source fires
        case kCFRunLoopBeforeWaiting:
            NSLog(@"run loop before waiting");
            break;
            //Inside the event processing loop after the run loop wakes up, but before processing the event that woke it up.
            //This activity occurs only if the run loop did in fact go to sleep during the current loop
        case kCFRunLoopAfterWaiting:
            NSLog(@"run loop after waiting");
            break;
            //The exit of the run loop, after exiting the event processing loop.
            //This activity occurs once for each call to CFRunLoopRun and CFRunLoopRunInMode
        case kCFRunLoopExit:
            NSLog(@"run loop exit");
            break;
        default:
            break;
    }
}

runloop相关

  • touch event~
    当页面发生touch event时,系统的调用栈:
  • nstimer~
    当应用有定时器时,系统调用栈:

  • autorelease~
    UIKit 通过 RunLoopObserver 在 RunLoop 两次 Sleep 间对 Autorelease Pool 进行 Pop 和 Push 将这次 Loop 中产生的 Autorelease 对象释放。

    关于autorelease的机制,可以参考黑幕背后的Autorelease

  • GCD
    GCD中dispathc到main queue的block被分发到main runloop执行~

  • runloop & 线程
    • 对于主线程来说,runloop是默认启动的~
    • 对于其他线程来说,run loop默认是没有启动的。如果你需要更多的线程交互则可以手动配置和启动。

哪儿用到RunLoop了?

  • 在一定时间内监听事件或执行某种任务线程
//在30分钟内,每隔30s执行onTimerFired:
@autoreleasepool {
    NSRunLoop * runLoop = [NSRunLoop currentRunLoop];
    NSTimer * udpateTimer = [NSTimer timerWithTimeInterval:30
                                                    target:self
                                                  selector:@selector(onTimerFired:)
                                                  userInfo:nil
                                                   repeats:YES];
    [runLoop addTimer:udpateTimer forMode:NSRunLoopCommonModes];
    [runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:60*30]];
}
  • AFNetworking

    这也是我在好多blog看到过关于这个的解析~在最新的AFNetworking在3.x已经不再提供NSURLConnectionOperation的支持

以下代码摘自AFURLConnectionOperation.m中:

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
		 //监听某个 port,目的是让这个 Thread 不会回收
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
}

+ (NSThread *)networkRequestThread {
    static NSThread *_networkRequestThread = nil;
    static dispatch_once_t oncePredicate;
    dispatch_once(&oncePredicate, ^{
        _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
        [_networkRequestThread start];
    });

    return _networkRequestThread;
}

// 调用的代码块~
- (void)start {
    [self.lock lock];
    if ([self isCancelled]) {
        [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    } else if ([self isReady]) {
        self.state = AFOperationExecutingState;

        [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]];
    }
    [self.lock unlock];
}

为什么这么做?
问题:
NSOperation+NSURLConnection 并发模型会面临 NSURLConnection 下载完成前线程退出导致 NSOperation 对象接收不到回调
解决方法:
单独起一个global thread,内置一个runloop,所有的connection都由这个runloop发起,回调也是它接收,不占用主线程

而为什么如此创建一个runloop?
RunLoop 启动前内部必须要有至少一个 Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先创建了一个新的 NSMachPort 添加进去了。通常情况下,调用者需要持有这个 NSMachPort (mach_port) 并在外部线程通过这个 port 发送消息到 loop 内;但此处添加 port 只是为了让 RunLoop 不至于退出,并没有用于实际的发送消息。

  • TableView中实现平滑滚动延迟加载图片
UIImage *downloadedImage = ...;
[self.avatarImageView performSelector:@selector(setImage:)
     withObject:downloadedImage
     afterDelay:0
     inModes:@[NSDefaultRunLoopMode]];

利用CFRunLoopMode的特性,可以将图片的加载放到NSDefaultRunLoopMode的mode里,这样在滚动UITrackingRunLoopMode这个mode时不会被加载而影响到。

  • 异步测试
Boolean CTTRunLoopRunUntil(Boolean(^fulfilled_)(), Boolean polling_, CFTimeInterval timeout_)
{
    // Loop Observer Callback
    __block Boolean fulfilled = NO;
    void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) =
    ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        assert(!fulfilled); //RunLoop should be stopped after condition is fulfilled.
        // Check Condition
        fulfilled = fulfilled_();
        if(fulfilled) {
            // Condition fulfilled: stop RunLoop now.
            CFRunLoopStop(CFRunLoopGetCurrent());
        } else if(polling_) {
            // Condition not fulfilled, and we are polling: prevent RunLoop from waiting and continue looping.
            CFRunLoopWakeUp(CFRunLoopGetCurrent());
        }
    };
    
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting);
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    
    // Run!
    CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout_, false);

    CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
    CFRelease(observer);

    return fulfilled;
}

注意

  • Every thread has a single run loop object associated with it. In Cocoa, this object is an instance of the NSRunLoop class. In a low-level application, it is a pointer to a CFRunLoopRef opaque type.
  • Before you run a run loop on a secondary thread, you must add at least one input source or timer to it. If a run loop does not have any sources to monitor, it exits immediately when you try to run it.

参考文章

https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSRunLoop_Class/index.html#//apple_ref/occ/cl/NSRunLoop
http://blog.ibireme.com/2015/05/18/runloop/#mode
https://github.com/ming1016/study/wiki/CFRunLoop
http://bou.io/RunRunLoopRun.html
http://bou.io/CTTRunLoopRunUntil.html
http://www.cnblogs.com/zy1987/p/4582466.html
http://www.hrchen.com/2013/07/tricky-runloop-on-ios/