iOS-runloop 个人学习记录

/**
 *  运行run loop
 *
 *  @param rl              运行的RunLoop对象
 *  @param modeName        运行的mode名称
 *  @param seconds         run loop超时时间
 *  @param returnAfterSourceHandled true:run loop处理完事件就退出  false:一直运行直到超时或者被手动终止
 *  
 *
 *  @return 返回4种状态
 */

 SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled){
     // 根据modeName获得当前mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);

    // 保存上一次mode 并将runloop替换为当前mode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // 通知observer,进入RunLoop

    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);  // 调用 __CFRunLoopRun 真正开始run

    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); // 通知observer, runloop退出

    rl->_currentMode = previousMode; // 将runloop恢复为之前的mode
    return result; // 返回runloop run的返回值
}

学习参考:https://blog.csdn.net/u013378438/article/details/80239686

一、runloop的组成

 

iOS-runloop 个人学习记录

每个线程对应1个runloop

每个runloop只能在一个mode下运行

每个mode有很多事件源,timer,observer

1.runloop在run的时候

1.通知Observer,即将进入runloop。 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);

2.通知Observer,即将处理Timer。__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

3.通知Observer,即将处理source0。__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)

4.处理source0。if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) __CFRunLoopDoBlocks(rl, rlm);

5.如果有source1,跳到9。if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL))

6.通知Observer,即将休眠。__CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);

7.休眠,等待唤醒  -->(source1唤醒,Timer唤醒,runloop超时,外部手动唤醒)

(i)开始休眠,监听waitSet所置顶的mach port。__CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); 

8.通知Observer,被唤醒。__CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);

9.处理唤醒时收到的消息,之后调回2

10.通知Observer,即将退出runloop。__CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit)

2.runloop的结构

runloop被唤醒通知app做下一步任务时的回调函数,大多数是以下6个

__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

Runloop对应的数据结构为

// Foundation:
NSRunLoop : NSObject

// Core Foundation:
struct __CFRunLoop
__CFRunLoop * CFRunLoopRef

struct __CFRunLoop {

    __CFPort _wakeUpPort;           // 唤醒runloop的pot 
    CFMutableSetRef _commonModes;//被加入到common mode中的mode
    CFMutableSetRef _commonModeItems;//mode下的items(source/timer/obserer)
    CFRunLoopModeRef _currentMode;//当前的mode
    CFMutableSetRef _modes;//Runloop所有的modes
    ...
};

当runloop没有任务处理,休眠时,在xcode点击暂定,会看到主线程停留在mach_msg_trap(),这个就是runloop接收消息的函数,如果没有port送消息过来,runloop就继续等待。

二、Thread & Runloop

每个Thread都有一个Runloop,但是除了系统会为主线程自动创建一个Runloop,子线程需要手动获取runloop,并且是懒加载的

NSRunLoop currentRunLoop];
[NSRunLoop mainRunLoop]
// 其对应的CF源码为:
CF_EXPORT CFRunLoopRef CFRunLoopGetCurrent(void);
CF_EXPORT CFRunLoopRef CFRunLoopGetMain(void);

CFRunLoopRef CFRunLoopGetMain(void) {
    CHECK_FOR_FORK();
    static CFRunLoopRef __main = NULL; // 主线程会存在一个静态变量
    if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
    return __main;
}

CFRunLoopRef CFRunLoopGetCurrent(void) {
    CHECK_FOR_FORK();
    CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);//子线程会存在TSD(线程私有数据)
    if (rl) return rl;
    return _CFRunLoopGet0(pthread_self());
}
这两个函数最终都会调用_CFRunLoopGet0(pthread_t t)创建线程,看一下这个方法实现了什么

static CFMutableDictionaryRef __CFRunLoops = NULL;//全局变量,存储线程对应的runloop
static CFLock_t loopsLock = CFLockInit;
CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
...
 CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); // 在静态全局变量__CFRunLoops寻找线程t所对应的RunLoop(key为线程自身)
    __CFUnlock(&loopsLock);
    if (!loop) { // 没找到
    CFRunLoopRef newLoop = __CFRunLoopCreate(t); // 创建一个新的Loop
        __CFLock(&loopsLock);
    loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); // 为什么get两次?这里猜测是因为保证线程安全,保证一个线程只有一个唯一的runloop。
    if (!loop) {
        CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); // 将新创建的Loop存入__CFRunLoops字典
        loop = newLoop;
    }
        // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it
        __CFUnlock(&loopsLock);
    CFRelease(newLoop);
    }
    if (pthread_equal(t, pthread_self())) { // 若当前调用线程和t是一个线程,则同时设置当前线程的TSD
        _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
        if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
            _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
        }
    }
    return loop; // 返回Loop
}

由代码可知

1.Runloop和Thread是一一对应的。CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));

2.Thread默认是没有对应Runloop的,只有主动get的时候才会懒加载,创建一个Runloop

3.所有的Thread对应的Runloop都被存储在__CFRunLoops字典中,同时,主线程有个static CFRunLoopRef  __main,子线程在TSD中存储,方便快速获取。

三、RunLoop Mode

RunLoop只有在Run的时候才会处理具体事务。每次run,都必须指定有个mode,mode指定了这次runloop可以处理的任务,对于不属于当前mode的任务,则需要切换mode重新调用run方法,才能被处理。

static int32_t __CFRunLoopRun(CFRunLoopRef rl,             // runloop will run
                              CFRunLoopModeRef rlm,        // the runloop 's mode to run
                             Boolean stopAfterHandle,      // if Yes, exit runloop after handle on source
                             CFRunLoopModeRef previousMode) // previous runloop mode

RunLoop,RunLoop mode和RunLoop items的关系如下图所示

iOS-runloop 个人学习记录一个Runloop可以有多个mode,每个mode下有soure0、souce1、timer和observers,当runloop休眠时(mach_msg_trap状态),外部可以通过wakeUpPort和timerPort将runloop唤醒。

runloop的代码结构

struct __CFRunLoopMode {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;  /* must have the run loop locked before locking this */
    CFStringRef _name;
    Boolean _stopped;
    char _padding[3];
    CFMutableSetRef _sources0; 
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    CFMutableDictionaryRef _portToV1SourceMap;
    __CFPortSet _portSet;
    CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    dispatch_source_t _timerSource;
    dispatch_queue_t _queue;  // dispatch timer 所在的queue
    Boolean _timerFired; // set to true by the source when a timer has fired
    Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
    mach_port_t _timerPort;
    Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
    DWORD _msgQMask;
    void (*_msgPump)(void);
#endif
    uint64_t _timerSoftDeadline; /* TSR */
    uint64_t _timerHardDeadline; /* TSR */
};

回看CFRunLoop中的mode相关的结构:

struct __CFRunLoop {
   ...
    CFMutableSetRef _commonModes;  // set:被加入到common mode中的mode
    CFMutableSetRef _commonModeItems; // set: 被加入到common mode 下的items(source/timer/observer )
    CFRunLoopModeRef _currentMode; // 当前的mode
    CFMutableSetRef _modes;  // RunLoop所有的modes
    ...
};

一个普通的mode可以把自己标记为common,,做法是,系统将该mode的name添加到_commonModes中。

当item被加入到commonModes时,首先会在runloop的commonModeItems加入一个条目,然后遍历所有的_commonModes,将该item加入到已经标记为common的mode下。

当一个mode作为整体被加入commonModes下时,系统会调用void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName)方法,在方法内部,首先把mode置为common,把mode的name加入到commonModes,然后把commonItems中所有的元素添加到这个mode中,但mode中的item不会被加入到commonItems中,这意味着当mode作为整体加入到commonModes中时,mode可以相应commonModeItems事件,而本身自带的mode items,在别的被标记为common的mode中,却不会响应

当我们将任务交给runloop时,需要制定哪个mode下处理,如果不制定,默认在default mode下处理。

一个任务可以提交多个mode,但向同一个mode多次提交同一个任务,则mode中仅会保存一个任务,代码中有类似判断

if(!CFSetContainsValue(rlm->_sources0, rls)) {
    // 将任务加入到mode中
}

timer是基于Runloop实现的,我们在创建timer时,可以指定timer的mode,如果没指定,默认是在default mode下

NSTimer *timer = [NSTimer timerWithTimeInterval:1 repeats:NO block:^(NSTimer * _Nonnull timer) {
       NSLog(@"do timer");
    }];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; // 指定timer在common modes(default mode + event tracking mode) 下运行

 

四、RunLoop Source

 

RunLoop能够哦处理的事件分为Input source和timer。

Input source分为 source0和source1,均有结构体__CFRunLoopSource表示

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;         /* 优先级;越小,优先级越高。可以是负数。immutable */
    CFMutableBagRef _runLoops;
    union {  // 联合,用于保存source的信息,同时可以区分source是0还是1类型
        CFRunLoopSourceContext version0;    /* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;   /* immutable, except invalidation */
    } _context;
};

typedef struct {
    CFIndex version;      // 类型:source0
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
    void    (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void    (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
    void    (*perform)(void *info);  // call out 
} CFRunLoopSourceContext;

typedef struct {
    CFIndex version;  // 类型:source1
    void *  info;
    const void *(*retain)(const void *info);
    void    (*release)(const void *info);
    CFStringRef (*copyDescription)(const void *info);
    Boolean (*equal)(const void *info1, const void *info2);
    CFHashCode  (*hash)(const void *info);
#if (TARGET_OS_MAC && !(TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)) || (TARGET_OS_EMBEDDED || TARGET_OS_IPHONE)
    mach_port_t (*getPort)(void *info);//比source0多了一个port,少了个cancel
    void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
#else
    void *  (*getPort)(void *info);
    void    (*perform)(void *info); // call out
#endif
} CFRunLoopSourceContext1;

soure0 VS souce1

相同点:

1.都是__CFRunLoopSource类型

2.都是要被Sinaled后,才被处理

3.处理时,都是调用__CFRunLoopSource._content.version(0?1).perform

不同

1.source0需要手动signaled,source1系统会自动signaled

2.source0需要手动唤醒RunLoop,才能被处理。CFRunLoopWakeUp(CFRunLoopRef rl)。而source1会通过mach port 自动唤醒RunLoop来处理。

3.source1 由RunLoop和内核管理,mach port驱动,source0则偏向应用层一些,如果UIEvent处理,会以souce0的形势发给Main RunLoop

Timer

我们经常用的timer有

1.NSTimer,performSelector:afterDelay(由RunLoop处理,内部结构为CFRunLoopTimerRef)

2.GCD Timer(由GCD自己实现,不通过RunLoop)

3.CADisplayLink(通过向RunLoop投递souce1实现回调)

NSTimer & PerformSelector:afterDelay:

NSTimer在CF源码中的结构

struct __CFRunLoopTimer {
    CFRuntimeBase _base;
    uint16_t _bits;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFMutableSetRef _rlModes;
    CFAbsoluteTime _nextFireDate;
    CFTimeInterval _interval;       /* immutable */
    CFTimeInterval _tolerance;          /* mutable */
    uint64_t _fireTSR;          /* 触发时间,TSR units */
    CFIndex _order;         /* immutable */
    CFRunLoopTimerCallBack _callout;    /* immutable */ // timer 回调
    CFRunLoopTimerContext _context; /* immutable, except invalidation */
};

1.当NSTimer被加入到RunLoop的某个mode下时,根据timer是否设置了tolerance,如果没有设置,调用底层的XNU内核的mk_timer注册一个mach-port,如果设置了tolerance,则注册一个GCD Timer。

2.当XNU内核或GCD管理的timer的fire time到了,通过对应的mach port,唤醒RunLoop对应的timePort或GCD queue port

3.RunLoop执行__CFRunLoopDoTimers,其中会调用__CFRunLoopDoTimer,DoTimer方法里会根据当前的mach time和Timer的fireTSR属性,判断fireTSR是否<当前的mach time,如果小于,调用__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__来回调到timer的fire函数。触发timer,并且更新下一次fire时间的fireTSR

timer事件触发时的函数堆栈:

iOS-runloop 个人学习记录

PerformSelector:afterDealy

底层实现实质实当前线程的runloop的defaultMode下加了一个timer,因此,在没有runloop启动的线程下,该函数是无法使用的。

PerformSelectorOnThread(mainThread):

在目标线程的Default mode 下添加一个source0,在source0的回调中,会执行我们的selector,因此,在线程没有启动runloop的情况下,PerformSelectorOnThread也是不能生效的

iOS-runloop 个人学习记录

dispatch to main queue

当调用 dispatch_async(dispatch_get_main_queue(), block) 时,libDispatch 会向主线程的 RunLoop 发送消息,RunLoop会被唤醒,并从消息中取得这个 block,并在回调 CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE() 里执行这个 block。但这个逻辑仅限于 dispatch 到主线程,dispatch 到其他线程仍然是由 libDispatch 处理的。 

当我们在dispatch main queue block中下断点时,可以看到如下调用堆栈: 
 iOS-runloop 个人学习记录

Observer 

Observer在CF中的结构如下:

struct __CFRunLoopObserver {
    CFRuntimeBase _base;
    pthread_mutex_t _lock;
    CFRunLoopRef _runLoop;
    CFIndex _rlCount;
    CFOptionFlags _activities;      /*所监听的事件,通过位异或,可以监听多种事件 immutable */
    CFIndex _order;         /* 优先级 immutable */
    CFRunLoopObserverCallBack _callout; /* observer 回调 immutable */
    CFRunLoopObserverContext _context;  /* immutable, except invalidation */
};

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),    // 进入RunLoop循环(这里其实还没进入)
    kCFRunLoopBeforeTimers = (1UL << 1),  // RunLoop 要处理timer了
    kCFRunLoopBeforeSources = (1UL << 2), // RunLoop 要处理source了
    kCFRunLoopBeforeWaiting = (1UL << 5), // RunLoop要休眠了
    kCFRunLoopAfterWaiting = (1UL << 6),   // RunLoop醒了
    kCFRunLoopExit = (1UL << 7),           // RunLoop退出(和kCFRunLoopEntry对应)
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

Observer的作用是可以让外部监听RunLoop的运行状态,从而根据不同时机,做一些操作。系统会在APP启东时,向main RunLoop里注册了两个Observer,其回调都是_wrapRunLoopWithAutoreleasePollHandler().

第一个Observer监听的时间是Entry,其回调内会调用_objc_autoreleasePoolPush()创建自动释放池,其order是-2147483647,优先级最高,保证创建释放池发生再其他所有回调之前。

第二个Observer监视了两个事件:BeforeWaiting时调用_objc_autoreleasePoolPop()和_objc_autoreleasePoolPush(),释放旧池创建新池。Exit时调用_objc_autoreleasePoolPop()释放自动释放池。这个Observer的order是21477483647,优先级最低,保证其释放池发生在所有回调之后。

在主线程执行的代码,通常是写在诸如事件回调、timer回调内的。这些回调会被RunLoop创建好的AutoreleasePool环绕着,所以不会出现内存泄露,开发者也不必显示创建Pool了

五、RunLoop源码剖析

/**
 *  运行run loop
 *
 *  @param rl              运行的RunLoop对象
 *  @param modeName        运行的mode名称
 *  @param seconds         run loop超时时间
 *  @param returnAfterSourceHandled true:run loop处理完事件就退出  false:一直运行直到超时或者被手动终止
 *  
 *
 *  @return 返回4种状态
 */

 SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled){
     // 根据modeName获得当前mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);

    // 保存上一次mode 并将runloop替换为当前mode
    CFRunLoopModeRef previousMode = rl->_currentMode;
    rl->_currentMode = currentMode;
    int32_t result = kCFRunLoopRunFinished;

    if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // 通知observer,进入RunLoop

    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);  // 调用 __CFRunLoopRun 真正开始run

    if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); // 通知observer, runloop退出

    rl->_currentMode = previousMode; // 将runloop恢复为之前的mode
    return result; // 返回runloop run的返回值
}
核心是__CFRunLoopRun
/**
 *  运行run loop
 *
 *  @param rl              运行的RunLoop对象
 *  @param rlm             运行的mode
 *  @param seconds         run loop超时时间
 *  @param stopAfterHandle true:run loop处理完事件就退出  false:一直运行直到超时或者被手动终止
 *  @param previousMode    上一次运行的mode
 *
 *  @return 返回4种状态
 */
 static int32_t __CFRunLoopRun(CFRunLoopRef rl, 
                               CFRunLoopModeRef rlm,
                               CFTimeInterval seconds, 
                               CFRunLoopModeRef previousMode) {
    // 借助GCD timer,设置runloop的超时时间
    dispatch_source_t timeout_timer = ...
    // 超时回调函数 __CFRunLoopTimeout
    dispatch_source_set_event_handler_f(timeout_timer, __CFRunLoopTimeout); 
    // 超时取消函数  __CFRunLoopTimeoutCancel
    dispatch_source_set_cancel_handler_f(timeout_timer, __CFRunLoopTimeoutCancel); 

    // 进入do while循环,开始run
    int32_t retVal = 0; // runloop run返回值,默认为0,会在do while中根据情况被修改,当不为0时,runloop退出
    do{
        mach_port_t livePort = MACH_PORT_NULL; // 用于记录唤醒休眠的RunLoop的mach port,休眠前是NULL
        __CFPortSet waitSet = rlm->_portSet; // 取当前mode所需要监听的mach port集合,用于唤醒runloop(__CFPortSet 实际上是unsigned int 类型)

        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);  // 通知 即将处理 Timers
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); // 通知 即将处理 sources

        // 处理提交到runloop的blocks
        __CFRunLoopDoBlocks(rl, rlm);

        // 处理 source0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); 
        if (sourceHandledThisLoop) {
            __CFRunLoopDoBlocks(rl, rlm); // 处理提交的runloop的block
        }

        // 如果有source1被signaled,则不休眠,直接跳到handle_msg来处理source1
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg; 
         }

        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); // 通知observer before waiting

        // ****** 开始休眠 ******
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy); // 调用__CFRunLoopServiceMachPort, 监听waitSet所指定的mach port端口集合, 如果没有port message,进入 mach_msg_trap, RunLoop休眠,直到收到port message或者超时

      // ****** 休眠结束 ******
      __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); // 通知observer, runloop醒了

handle_msg:; // 处理事件
     // 根据唤醒RunLoop的livePort值,来进行对应逻辑处理
     if (MACH_PORT_NULL == livePort) { // MACH_PORT_NULL: 可能是休眠超时,啥都不做
            CFRUNLOOP_WAKEUP_FOR_NOTHING();
            // handle nothing
      } else if (livePort == rl->_wakeUpPort) { // rl->_wakeUpPort: 被其他线程或进程唤醒,啥都不做
            CFRUNLOOP_WAKEUP_FOR_WAKEUP();
            // do nothing on Mac OS
      } else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) // rlm->_timerPort: 处理nstimer 消息
         if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                // Re-arm the next timer
                __CFArmNextTimerInMode(rlm, rl);
            }
      }else if (livePort == dispatchPort) // dispatchPort:处理分发到main queue上的事件
      {
          __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
      }else {   // 其余的,肯定是各种source1 事件
          __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
          if (NULL != reply) { // 如果有需要回复soruce1的消息,则回复
                (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                    CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
           }
      }
     // ****** 到这里就结束了对livePort的处理 ******

     __CFRunLoopDoBlocks(rl, rlm);  // 处理提交到runloop的blocks

     // 检查runloop是否需要退出
    if (sourceHandledThisLoop && stopAfterHandle) { // case1. 指定了仅处理一个source 退出kCFRunLoopRunHandledSource
        retVal = kCFRunLoopRunHandledSource;
    } else if (timeout_context->termTSR < mach_absolute_time()) { // case2. RunLoop 超时
        retVal = kCFRunLoopRunTimedOut;
    } else if (__CFRunLoopIsStopped(rl)) { // case3. RunLoop 被终止
        __CFRunLoopUnsetStopped(rl);
        retVal = kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {  // case4. RunLoop Mode 被终止
        rlm->_stopped = false;
        retVal = kCFRunLoopRunStopped;
    } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { // case5. RunLoop Mode里面没有任何要被处理的事件了(没有source0,source1, timer,以及提交到当前runloop mode的block)
        retVal = kCFRunLoopRunFinished;
    }
    }while(0 == retVal);

    // runloop循环结束,返回退出原因
    return retVal;
}

总结一下:

RunLoop仅会对当前mode下的source,timer和observer进行处理
RunLoop在各个状态下会对observer发送相应通知。通知顺序是: 
进入RunLoop循环前: (1)kCFRunLoopEntry 
进入循环((2)–(4)反复循环): 
(2)kCFRunLoopBeforeTimers -> (3)kCFRunLoopBeforeSources -> (4)kCFRunLoopBeforeWaiting -> (5)kCFRunLoopAfterWaiting 
退出循环:(6)kCFRunLoopExit
RunLoop通过调用__CFRunLoopServiceMachPort,通过Mach Port监听Ports来实现休眠(陷入mach_msg_trap状态)。可以唤醒RunLoop的事件包括: 
(1) Mach Port监听超时 
(2)被其他线程\进程唤醒 
(3)有Timer事件需要执行 
(4)有提交到main queue上的block(当前RunLoop是main RunLoop时才有这种情况) 
(5)被source1唤醒
RunLoop退出的可能原因有: 
(1)RunLoop超时 
(2)处理完事件就退出stopAfterHandle 
(3)RunLoop被终止 
(4)RunLoop Mode被终止 
(5)RunLoop Mode里面source0, source1, timer队列都空了,没啥要处理了

当APP没有任何事件处理时,通过xcode的APP暂停键,可以看到APP处于mach_msg_trap,等待消息状态

iOS-runloop 个人学习记录

苹果用RunLoop实现的功能

事件响应

main RunLoop会在所有mode下注册source0事件源

<CFRunLoopSource 0x6040001729c0 [0x1029cebb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x60400014e0d0, callout = __handleEventQueue (0x1034e5dcc)}}

他们有一个共同的回调函数__handleEventQueue,但由于他们是source0的事件源,无法自己唤醒,所以需要一个source1去唤醒,这个source1就被一个名字叫做com.apple.uikit.eventfetch-thread的子线程的runloop注册,它的回调是__IOHIDEventSystemClientQueueCallback。因为source1是通过端口监听,可以被唤醒的。所以在soure1被唤醒时,会将manRunLoop的source0置为signalled=YES的状态,同时唤醒mainRunLoop。manLoop则调用__handleEventQueue进行事件处理。

iOS-runloop 个人学习记录

 

手势识别

iOS的手势识别也依赖于RunLoop。UIKit会向mainRunLoop注册一个observer,该observer监听mainRunLoop的kCFRunLoopBeforeWaing事件,每当main RunLoop即将休眠时,该observer被处罚,同时调用函数_UIGestureRecognizerUpdateObserver。_UIGestureRecognizerUpdateObserver会检测当前需要被更新的状态的recognizer。

如果有手势处罚,如果有手势被触发,在_UIGestureRecognizerUpdateObserver回调中会借助UIKit一个内部类UIGestureEnvironment 来进行一系列处理,会向APP的event queue投递一个gesture event,内部会调用__handleEventQueueInternal处理该gesture event,最终回到我们自己写的gesture回调中。

iOS-runloop 个人学习记录

界面刷新

当我们需要界面刷新的,如UIView/CALayer调用setNeedsLayout/setNeedsDisplay,或更新了UIView的frame或UI层次。其实系统并不是立刻去刷新界面的,而是先提交UI刷新请求,等到下次一mainRunLoop循环时,集中处理,而这是通过监听mainRunLoop的beforeWaiting和Exit通知实现的。

该observer的回调是

_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv

其内部会调用

CA::Transaction::observer_callback // 位于QuartzCore 中 

该函数中,会将所有的界面刷新请求提交,刷新界面,以及调用相关回调。iOS-runloop 个人学习记录

上一篇:JZOJ 4788. 【NOIP2016提高A组模拟9.17】序列


下一篇:Linux 查找当前目录下所有包含指定内容的文件