30、iOS底层分析 - RunLoop

RunLoop

定义:

 RunLoop 是与线程相关的基础架构中的一部分,他是一个处理事件的循环(线程进入这个循环,运行事件处理程序来响应传入的事件)。RunLoop 的目的是当有事件需要处理时,线程是活跃的、忙碌的,当没有事件后,线程进入休眠。
 RunLoop 的是底层实现是一个 do while 循环,但不是简单的do while 有 sleep,在需要休眠的时候休眠,需要唤醒的时候被唤醒

NSRunLoop是iOS的CoreFoundation的一部分,Foundation对Core Foundation又做了一层封装。

RunLoop 一般是通过Run来运行,查看源码。可以发现确实是一个do while循环

void CFRunLoopRun(void) {    /* DOES CALLOUT */
    int32_t result;
    do {
        result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
        CHECK_FOR_FORK();
    } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}

 作用:

  1.  保持程序的持续运行
  2.  处理APP中各种事件(触摸、定时器、performSelector)
  3.  节省cpu 资源、提升程序的性能:该做事就做事,该休息就休息

 我们的所有 port响应、custom(自定义) 、 NSTimer、回调block 等等都依赖于 RunLoop 。

处理 Port
定制的src
我的选择器
定时器触发

等等都是需要RunLoop来处理的。

现在就以NSTimer为例看一下
通过打断点,控制台 bt 查看一下全部的堆栈信息。

 frame #13: 0x000000010d1cd606 QuartzCore`CA::Transaction::commit() + 610
 frame #14: 0x000000011791399c UIKitCore`_afterCACommitHandler + 245
 frame #15: 0x000000010cc312c7 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__ + 23
 frame #16: 0x000000010cc2b78e CoreFoundation`__CFRunLoopDoObservers + 430

NSTimer 的堆栈信息里可以看到调用了一个  __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

在RunLoop的源码中找一下,发现了如下代码。这么说明Timer 的调用是需要RunLoop来执行的。其他的例如通知、block、触摸事件source0 等也是一样的都在RunLoop 里面。

 static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__() __attribute__((noinline));
 static void __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(CFRunLoopTimerCallBack func, CFRunLoopTimerRef timer, void *info) {
     if (func) {
     func(timer, info);
     }
     asm __volatile__(""); // thwart tail-call optimization
 }

总结一下

observer 还有一些下层处理,一些通知等。

source0 是用户触发信息、source1是系统触发。后面具体说明。

RunLoop与线程间的关系

先从主运行循环开始看起。

 //    主运行循环
 CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
 //    当前运行循环
 CFRunLoopRef currenRunLoop = CFRunLoopGetCurrent();

查看拿一下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;
 }

既然是通过  _CFRunLoopGet0 返回的就看一下_CFRunLoopGet0,传了一个参数pthread_main_thread_np()。也即是下边的 pthread_t tpthread_main_thread_np() 就是主线程,默认是主线程。判断一下线程情况,如果线程不存在就改成默认的主线程。

创建一个字典,创建一个线程。通过key Value 的形式,将RunLoop 和线程进行一对一绑定。

例如:主线程 与 主运行循环。 dict[@"pthread_main_thread_np"] = mainLoop

CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        //传进来的 t  判断线程情况如果线程不存在,就改成默认的主线程
        if (pthread_equal(t, kNilPthreadT)) {
            t = pthread_main_thread_np();
        }
        __CFLock(&loopsLock);
        if (!__CFRunLoops) {
            __CFUnlock(&loopsLock);
 
            //创建一个字典,
            CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
            
            //通过 pthread_main_thread_np 创建一个主线程,
            CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
            
            // 进行绑定 dict[@"pthread_main_thread_np"] = mainLoop
            //通过 key value 的形式将 字典 和 主线程、主运行循环RunLoop 进行绑定
            CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
 
            if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
                CFRelease(dict);
            }
            CFRelease(mainLoop);
            __CFLock(&loopsLock);
        }
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
        //不是主线程的绑定
        if (!loop) {
            CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&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
            __CFUnlock(&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;
    }
//线程的创建
    static CFRunLoopRef __CFRunLoopCreate(pthread_t t) {
        CFRunLoopRef loop = NULL;
        CFRunLoopModeRef rlm;
        uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase);
        loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, CFRunLoopGetTypeID(), size, NULL);
        if (NULL == loop) {
            return NULL;
        }

真正的RunLoop 是__CFRunLoop,是一个对象。继续查看源码

//RunLoop 对象
    struct __CFRunLoop {
        CFRuntimeBase _base;
        pthread_mutex_t _lock;            // locked for accessing mode list //
        __CFPort _wakeUpPort;            // used for CFRunLoopWakeUp
        Boolean _unused;
        volatile _per_run_data *_perRunData;    //一些端口信息         // reset for runs of the run loop
        pthread_t _pthread;                     //线程
        uint32_t _winthread;
        CFMutableSetRef _commonModes;           //mode 集合 例如UIModel 等
        CFMutableSetRef _commonModeItems;       //item 结合
        CFRunLoopModeRef _currentMode;          //当前的mode
        CFMutableSetRef _modes;
        struct _block_item *_blocks_head;
        struct _block_item *_blocks_tail;
        CFAbsoluteTime _runTime;
        CFAbsoluteTime _sleepTime;
        CFTypeRef _counterpart;
    };

看到这里就看到了真正的 RunLoop ,里面有一个 modes,  items。 还有当前的mode

 查看 CFRunLoopModeRef

typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    
    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;           //一个结合属性,_sources1
        CFMutableArrayRef _observers;        //mode 对observer(通知) 一对多
        CFMutableArrayRef _timers;           //mode 对timer(计时器) 一对多
        CFMutableDictionaryRef _portToV1SourceMap;
        __CFPortSet _portSet;
        CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS

可以看到 modeCFRunLoopSourceCFRunLoopTimer 、  CFRunLoopObserver 是一对多
 一个RunLoop包含若干个Mode,每个Mode包含若干个Source/Timer/Observer/Port。当启动一个RunLoop时会先指定一个Mode,检查指定Mode是否存在以及Mode中是否含有Source和Timer,如果Mode不存在或者Mode中无Source和Timer,认为该Mode是一个空的Mode,RunLoop就直接退出。

子线程RunLoop 默认不启动。

 子线程并不是必须要设置运行RunLoop 才能执行任务,比如说只是简单的子线程中处理一个耗时任务等。但是如下场景是需要启动RunLoop 的。
 1、使用NSPort 或者自定义输入源与其他线程通信
 2、在线程上使用计时器 NSTimer。
 3、在一个Cocoa 应用中使用 performSelector 相关方法
 4、使用功能线程常驻,在该线程定期执行任务。

例如:

-(void)testThreadRunLoop
{
//    子线程 RunLoop 默认不启动
    self.isStopping = NO;
    LJLThread * thread = [[LJLThread alloc] initWithBlock:^{
//        thread.name = nil //因为这个变量只是捕捉
//        LJLThread * thread = nil;
//        thread = 初始化 捕捉一个 nil 进来
        NSLog(@"%@  --  %@",[NSThread currentThread],[[NSThread currentThread] name]);
        [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"hello wrod");  //退出线程 -- 结果runloop 也停止了
            if (self.isStopping) {
                [NSThread exit];
            }
        }];
//        [[NSRunLoop currentRunLoop] run];
    }];
    thread.name = @"ljlcode.com";
    [thread start];
}
//LJLThread.h
#import <Foundation/Foundation.h>
@interface LJLThread : NSThread
@end

//LJLThread.m
#import "LJLThread.h"
@implementation LJLThread
- (void)dealloc
{
    NSLog(@"线程销毁了");
}
@end

如果 //[[NSRunLoop currentRunLoop] run]; 没有打开的话,那么Timer并没有执行,线程直接销毁了。

2020-03-24 10:22:52.919109+0800 filedome[12692:401566] <LJLThread: 0x600002386880>{number = 4, name = ljlcode.com}  --  ljlcode.com
2020-03-24 10:22:52.920025+0800 filedome[12692:401566] 线程销毁了

打开 [[NSRunLoop currentRunLoop] run]; 。正常调用了

2020-03-24 10:24:31.242384+0800 filedome[12800:402809] <LJLThread: 0x6000038f99c0>{number = 3, name = ljlcode.com}  --  ljlcode.com
2020-03-24 10:24:32.245428+0800 filedome[12800:402809] hello wrod
2020-03-24 10:24:33.246504+0800 filedome[12800:402809] hello wrod
2020-03-24 10:24:34.246219+0800 filedome[12800:402809] hello wrod

最重要的就是 timer、observer、source

mode 的类型

 //    主运行循环
 CFRunLoopRef mainRunLoop = CFRunLoopGetMain();
 //    当前运行循环
 CFRunLoopRef currenRunLoop = CFRunLoopGetCurrent();
//在这之后打上断点

打断点lldb调试。在控制台输入   po CFRunLoopCopyAllModes(currenRunLoop)  查看一下当前运行循环。

 (lldb) po CFRunLoopCopyAllModes(currenRunLoop)
 <__NSArrayM 0x600000a97ea0>(
 UITrackingRunLoopMode,
 GSEventReceiveRunLoopMode,
 kCFRunLoopDefaultMode,     //默认的DefaultMode 用的最多的
 kCFRunLoopCommonModes
 )

在UI响应的时候,timer 是无法进行计时的 只需要把mode 切换一下,切到 UITrackingRunLoopMode 等mode。切换的话必须要退出,同一时间不能有两种mode同时运行
 


 NSTimer

-(void)timerDemo
{
//    CFRunLoopMode 研究
    CFRunLoopRef lp     = CFRunLoopGetCurrent();
    CFRunLoopMode mode  = CFRunLoopCopyCurrentMode(lp);
    NSLog(@"mode == %@",mode);
    CFArrayRef modeArray = CFRunLoopCopyAllModes(lp);
    NSLog(@"modeArray == %@",modeArray);
    
    NSTimer * timer = [NSTimer timerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
        NSLog(@"fier in home -- %@",[[NSRunLoop currentRunLoop] currentMode]);
    }];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}

 将执行添加到timer里面,然后把timer添加到RunLoop里面,那么对RunLoop又有什么影响呢?

源码中搜一下 AddTimer,找到了 CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);这样的一些代码。  随便找一个然后点  CFRunLoopAddTimer 进去。

先判断 modeName  是否是 commonMode (并不是一个具体的mode类型,是一个抽象mode),里面有defaultMode等,如果没有就会默认标记为 defaultMode

    void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
        CHECK_FOR_FORK();
        if (__CFRunLoopIsDeallocating(rl)) return;
        if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return;
        __CFRunLoopLock(rl);
        //判断是否是 kCFRunLoopCommonModes ,kCFRunLoopCommonModes 并不是一个具体的mode 类型 里面 kCFAllocatorSystemDefault 或者其他的mode 类型,是一个抽象mode
        if (modeName == kCFRunLoopCommonModes) {
            CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
            //如果 commonMode 中没有,默认标记为 kCFAllocatorSystemDefault   DefaultMode
            if (NULL == rl->_commonModeItems) {
                rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
            }
            CFSetAddValue(rl->_commonModeItems, rlt);
            if (NULL != set) {
                CFTypeRef context[2] = {rl, rlt};
                /* add new item to all common-modes */
                //运行设置到 context 中
                CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
                CFRelease(set);
            }
        } else {

判断是否是 kCFRunLoopCommonModes ,kCFRunLoopCommonModes 并不是一个具体的mode 类型 里面 kCFAllocatorSystemDefault 或者其他的mode 类型,是一个抽象mode
 如果 commonMode 中没有,默认标记为 kCFAllocatorSystemDefault   DefaultMode
 
 然后就会运行,然后添加到 set 集合里面

CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
 CFRelease(set);

runloop 通过 run 启动 所以看一下runloop run。在看源码之前先看一下调用堆栈。看一下timer 的具体运行效果。
 NSTimer 调用 block 块里面是(NSLog()) 这里才开始 function(运行),在NSLog() 这行打断点,然后打印一下堆栈 控制台 bt

(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 10.1
  * frame #0: 0x000000010344ebbc filedome`__37-[LJLRunLoopViewController timerDemo]_block_invoke(.block_descriptor=0x00000001034547d0, timer=0x0000600003a3a880) at LJLRunLoopViewController.m:101:38
    frame #1: 0x0000000104166079 Foundation`__NSFireTimer + 83
    frame #2: 0x000000010470b5b4 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
    frame #3: 0x000000010470b1c2 CoreFoundation`__CFRunLoopDoTimer + 1026
    frame #4: 0x000000010470aa2a CoreFoundation`__CFRunLoopDoTimers + 266
    frame #5: 0x00000001047050cc CoreFoundation`__CFRunLoopRun + 2220
    frame #6: 0x00000001047044d2 CoreFoundation`CFRunLoopRunSpecific + 626
    frame #7: 0x0000000109fc42fe GraphicsServices`GSEventRunModal + 65
    frame #8: 0x000000010db71fc2 UIKitCore`UIApplicationMain + 140
......

可以看到,执行过程。

系统启动就启动了RunLoop(主运行循环中)   GSEventRunModal

然后就可以去查看__CFRunLoopRun 的源码,然后一层一层的去找。
 __CFRunLoopRun  ->  __CFRunLoopDoTimers  ->  __CFRunLoopDoTimer

    static int32_t __CFRunLoopRun__CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
        uint64_t startTSR = mach_absolute_time();
        
        ......
        
#if USE_DISPATCH_SOURCE_FOR_TIMERS
    else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
        CFRUNLOOP_WAKEUP_FOR_TIMER();
        if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
            // Re-arm the next timer, because we apparently fired early
            __CFArmNextTimerInMode(rlm, rl);
        }
    }
#endif
......

然后继续往下看 __CFRunLoopDoTimers

 static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) {    / * DOES CALLOUT * /
    Boolean timerHandled = false;
    CFMutableArrayRef timers = NULL;
    for (CFIndex idx = 0, cnt = rlm->_timers ? CFArrayGetCount(rlm->_timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(rlm->_timers, idx);
        
        if (__CFIsValid(rlt) && !__CFRunLoopTimerIsFiring(rlt)) {
            if (rlt->_fireTSR <= limitTSR) {
                if (!timers) timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeArrayCallBacks);
                CFArrayAppendValue(timers, rlt);
            }
        }
    }
    
    for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) {
        CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx);
        Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt);
        timerHandled = timerHandled || did;
    }
    if (timers) CFRelease(timers);
    return timerHandled;
}

1、遍历 CFArrayGetCount 拿到 CFRunLoopTimerRef 对象(因为在一个runloop中可能存在多个timer,要遍历拿到)准备。
2、准备完成之后,再次遍历拿出来 CFRunLoopTimerRef  timer 对象 然后 __CFRunLoopDoTimer
3、Boolean did  __CFRunLoopDoTimer给一个状态,判断doTimer 是否执行完,如果遍历执行完之后进行 CFRelease(timers) 释放

    static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) {    / * DOES CALLOUT * /
       
        ......
            
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info);
            CHECK_FOR_FORK();
            if (doInvalidate) {
                CFRunLoopTimerInvalidate(rlt);      / * DOES CALLOUT * /
            }
            if (context_release) {
                context_release(context_info);
            }
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
            __CFRunLoopTimerLock(rlt);
            timerHandled = true;
            __CFRunLoopTimerUnsetFiring(rlt);
        }

小结:timer 受 RunLoop直接管控,中间有一些mode 的处理,mode 中有DefaultMode 一些遍历等。

  1.  首先要收集 timers
  2.  收集完毕添加到 Array 里面
  3. 然后开始遍历整个 count 然后挨个 timer 的 doTimer
  4.  doTimer 里面进行回调  __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__

observer 同timer。

if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);

 block  的话想对简单,判断doit ,如果存在直接回调。

__CFRunLoopDoBlocks(rl, rlm);
static Boolean __CFRunLoopDoBlocks(CFRunLoopRef rl, CFRunLoopModeRef rlm) { // Call with rl and rlm locked
......
    while (item) {
        struct _block_item *curr = item;
        item = item->_next;
    Boolean doit = false;
    if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
            doit = CFEqual(curr->_mode, curMode) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
        } else {
            doit = CFSetContainsValue((CFSetRef)curr->_mode, curMode) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(commonModes, curMode));
    }
    if (!doit) prev = curr;
    if (doit) {
        ......
                __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
            did = true;
        }
       ......
    return did;
}

Source0  用户的点击等操作

source1  来自系统内核的一些端口信息。


 时间源会在预设的时间同步传递事件给对应的线程,计时器是线程通知自己做某事的一种方式。
 计时器并不是真正的实时的,当计时器未处于 RunLoop 当前监听的mode(切换mode 的话当前mode 必须要退出,同一时间不能有两种mode同时运行) ,那么计时器是不会计时调度任务,只有RunLoop 当前监听的mode 是计时器关联的 Mode 时,计时器才会开始任务。
 例如:

NSTimer 添加至主线程 RunLoop 的 DefaultMode 中,此时滑动 TableView  /  ScrollView 时,RunLoop 会切换至 TrackMode,计时器是不会调度任务的。如果RunLoop 在执行一个例程时。计时器触发了,那么计时器会等待 RunLoop 将该例程执行完成,在下一次的循环中处理。在RunLoop 未运行情况下,计时器永远不会触发任务。
 

 RunLoop
 会添加很多 item
 items - timer  source(0、1)  observer 、 block 是一种特殊的情况
 source0 来自用户的触发事件,例如屏幕点击等等
 source1 系统给的
 这些item 要依赖运行,要依赖 mode
 mode 有分为 commonMode 、mainMode 、currentMode、sleepMode 等等
 每个mode 又有source、observer、timer等
 
 

 RunLoopRun

item 的回调依赖于 RunLoop 的 run
查看源码。首先进到 void CFRunLoopRun(void) 进行do while 循环

    void CFRunLoopRun(void) {    / * DOES CALLOUT * /
        int32_t result;
        do {
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }

 CFRunLoopRun 会根据 result 是否停止或完成,来决定是否继续循环。所以我们进到 CFRunLoopRunSpecific 看一下。

  1.  先判断mode,因为run 需要依赖mode 去处理事务。 如果没找到 || mode中没有注册任何事件,则就此停止,不进入循环
  2.  如果当前的mode 和传过来的 mode 是不一样的,说明有问题,做一些处理(不用去研究)。
  3.  然后通知 observers : RunLoop 即将进入 loop
  4.  进入  __CFRunLoopRun 处理
  5.  最后通知 observer : RunLoop 即将退出

RunLoop进入 和退出都通过 observer 进行通知

    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     / * DOES CALLOUT * /
        CHECK_FOR_FORK();
        if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
        __CFRunLoopLock(rl);
        //根据modeName找到本次运行的mode
        CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
        //如果没找到 || mode中没有注册任何事件,则就此停止,不进入循环
        if (NULL == currentMode || __CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
            Boolean did = false;
            if (currentMode) __CFRunLoopModeUnlock(currentMode);
            __CFRunLoopUnlock(rl);
            return did ? kCFRunLoopRunHandledSource : kCFRunLoopRunFinished;
        }
        volatile _per_run_data *previousPerRun = __CFRunLoopPushPerRunData(rl);
        //取上一次运行的mode
        CFRunLoopModeRef previousMode = rl->_currentMode;
        //如果本次mode和上次的mode一致
        rl->_currentMode = currentMode;
        //初始化一个result为kCFRunLoopRunFinished
        int32_t result = kCFRunLoopRunFinished;
        
        if (currentMode->_observerMask & kCFRunLoopEntry )
            /// 1. 通知 Observers: RunLoop 即将进入 loop。
            __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        if (currentMode->_observerMask & kCFRunLoopExit )
            /// 10. 通知 Observers: RunLoop 即将退出。
            __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
        
        __CFRunLoopModeUnlock(currentMode);
        __CFRunLoopPopPerRunData(rl, previousPerRun);
        rl->_currentMode = previousMode;
        __CFRunLoopUnlock(rl);
        return result;
    }

__CFRunLoopRun

 1、取系统时间,用于判断超时。
 2、判断超时 三种情况,小于0已经超时。< TIMER_INTERVAL_LIMIT  或者是永不超时。
 3、判断超时需要一个计时器,用 GCD dispatch_source_create  然后绑定一个回调,限制超时
 时长 是 1.0e10  是科学技术法 = 1* 10^10
 4、根据超时 或者 完成等状态标记 retVal
 5、如果超时进行释放,释放之后返回 retVal,CFRunLoopRunSpecific 里面通知 Observers: RunLoop 即将退出。

/**
 *  运行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, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    //获取系统启动后的CPU运行时间,用于控制超时时间
    uint64_t startTSR = mach_absolute_time();
    
    // 判断当前runloop的状态是否关闭
    if (__CFRunLoopIsStopped(rl)) {
   ......
    
    //mach端口,在内核中,消息在端口之间传递。 初始为0
    mach_port_name_t dispatchPort = MACH_PORT_NULL;
    //判断是否为主线程
    Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
    //如果在主线程 && runloop是主线程的runloop && 该mode是commonMode,则给mach端口赋值为主线程收发消息的端口
    if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) dispatchPort = _dispatch_get_main_queue_port_4CF();
    
......
    dispatch_source_t timeout_timer = NULL;
    struct __timeout_context *timeout_context = (struct __timeout_context *)malloc(sizeof(*timeout_context));
    if (seconds <= 0.0) { // instant timeout
        seconds = 0.0;
        timeout_context->termTSR = 0ULL;
        // 1.0e10 == 1* 10^10
    } else if (seconds <= TIMER_INTERVAL_LIMIT) {
        //seconds为超时时间,超时时执行__CFRunLoopTimeout函数
        dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, DISPATCH_QUEUE_OVERCOMMIT);
        timeout_timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
        dispatch_retain(timeout_timer);
        ......
        dispatch_resume(timeout_timer);
    } else { // infinite timeout
        //永不超时 - 永动机
        seconds = 9999999999.0;
        timeout_context->termTSR = UINT64_MAX;
    }
    
    //标志位默认为true
    Boolean didDispatchPortLastTime = true;
    //记录最后runloop状态,用于return
    int32_t retVal = 0;
    
    // itmes
    do {
    ......    
    //超时释放
    if (timeout_timer) {
        dispatch_source_cancel(timeout_timer);
        dispatch_release(timeout_timer);
    } else {
        free(timeout_context);
    }

    return retVal;
}

如果超时了标记为超时  kCFRunLoopRunTimedOut

如果完成了,标记为  kCFRunLoopRunFinished

    /// 执行加入到Loop的block
    __CFRunLoopDoBlocks(rl, rlm);
    
    if (sourceHandledThisLoop && stopAfterHandle) {
        /// 进入loop时参数说处理完事件就返回。
        retVal = kCFRunLoopRunHandledSource;
    } else if (timeout_context->termTSR < mach_absolute_time()) {
        /// 超出传入参数标记的超时时间了
        retVal = kCFRunLoopRunTimedOut;
    } else if (__CFRunLoopIsStopped(rl)) {
        /// 被外部调用者强制停止了
        __CFRunLoopUnsetStopped(rl);
        retVal = kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        /// 自动停止了
        rlm->_stopped = false;
        retVal = kCFRunLoopRunStopped;
    } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
        /// source/timer/observer一个都没有了
        retVal = kCFRunLoopRunFinished;
    }
    /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
} while (0 == retVal);

if (timeout_timer) {
    dispatch_source_cancel(timeout_timer);
    dispatch_release(timeout_timer);
} else {
    free(timeout_context);
}

return retVal;
}

 一但runloop 退出,就会去执行例如 CFRunLoopRemoveAllSources、CFRunLoopRemoveTimer等。

    static void __CFRunLoopRemoveAllSources(CFRunLoopRef rl, CFStringRef modeName) {
        CHECK_FOR_FORK();
        CFRunLoopModeRef rlm;
        __CFRunLoopLock(rl);
        if (modeName == kCFRunLoopCommonModes) {
            if (NULL != rl->_commonModeItems) {
                CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
                if (NULL != set) {
                    CFSetApplyFunction(set, (__CFRunLoopRemoveSourcesFromCommonMode), (void *)rl);
                    CFRelease(set);
                }
            } else {
            }
        } else {
            rlm = __CFRunLoopFindMode(rl, modeName, false);
            if (NULL != rlm && NULL != rlm->_sources0) {
                CFSetRef set = CFSetCreateCopy(kCFAllocatorSystemDefault, rlm->_sources0);
                CFTypeRef context[2] = {rl, modeName};
                CFSetApplyFunction(set, (__CFRunLoopRemoveSourceFromMode), (void *)context);
                CFRelease(set);
            }
            if (NULL != rlm && NULL != rlm->_sources1) {
                CFSetRef set = CFSetCreateCopy(kCFAllocatorSystemDefault, rlm->_sources1);
                CFTypeRef context[2] = {rl, modeName};
                CFSetApplyFunction(set, (__CFRunLoopRemoveSourceFromMode), (void *)context);
                CFRelease(set);
            }
            if (NULL != rlm) {
                __CFRunLoopModeUnlock(rlm);
            }
        }
        __CFRunLoopUnlock(rl);
    }
void CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {
 CHECK_FOR_FORK();

RunLoop内部执行过程
 
 CFRunLoopRunSpecific
 1、首先根据modeName找到对应mode
 2、通知 observers: RunLoop 即将进入 loop
 3、内部函数,进入loop
 4、通知 observer: RunLoop 即将退出。
 return
 
 核心函数 do while
 __CFRunLoopRun
 1、通知 observers:即将处理timer 事件
 2、通知 observers:即将处理source 事件
 3、处理 Blocks
 4、处理 sources0
 5、if 处理sources0 返回为 YES
        处理Blocks
 6、判断有误端口号(source1)
        处理消息
 7、通知observers:即将进入休眠
 8、等待被唤醒
 9、通知 observers: 被唤醒,结束休眠
 10、if
    被timer 唤醒
        处理timers
    被 GCD 唤醒
        处理gcd
    被 source1 唤醒
        处理source1
 11、处理block
 
 使用系统级别的 observer 通知机制进行通知。
 RunLoop只能被 timer 或 gcd 或 source1系统唤醒。source1 系统消息。source0不能唤醒。
 唤醒后有几种响应。
 1、回调信息 HandledSource
 2、TimedOut 响应
 3、Stopped 分为两种,一种是人为的,另外一种是系统的。
 4、Finished 完成

    /// 执行加入到Loop的block
    __CFRunLoopDoBlocks(rl, rlm);
    
    if (sourceHandledThisLoop && stopAfterHandle) {
        /// 进入loop时参数说处理完事件就返回。
        retVal = kCFRunLoopRunHandledSource;
    } else if (timeout_context->termTSR < mach_absolute_time()) {
        /// 超出传入参数标记的超时时间了
        retVal = kCFRunLoopRunTimedOut;
    } else if (__CFRunLoopIsStopped(rl)) {
        /// 被外部调用者强制停止了
        __CFRunLoopUnsetStopped(rl);
        retVal = kCFRunLoopRunStopped;
    } else if (rlm->_stopped) {
        /// 自动停止了
        rlm->_stopped = false;
        retVal = kCFRunLoopRunStopped;
    } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
        /// source/timer/observer一个都没有了
        retVal = kCFRunLoopRunFinished;
    }
    /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
 

do while 循环中做两件事情,一、处理item  二、处理休眠事务

唤醒之后继续2步骤循环。
 
 CFRunLoopRef runloop = CFRunLoopGetCurrent();
 CFArrayRef allmodes = CFRunLoopCopyAllModes(runloop);
 拿到所有 mode,mode里面有所有的事务,所以可以在程序崩溃的时候通过while循环 然程序暂时不要崩溃,然后可以做一些操作,拿到崩溃信息等等。

简化后的流畅大概如下

SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    CHECK_FOR_FORK();
    if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
    __CFRunLoopLock(rl);
    
    /// 首先根据modeName找到对应mode
    CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
    
    /// 通知 Observers: RunLoop 即将进入 loop。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    
    /// 内部函数,进入loop
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    
    /// 通知 Observers: RunLoop 即将退出。
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
    return result;
}

/// 核心函数
static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
    int32_t retVal = 0;
    
    do {
        
        /// 通知 Observers: 即将处理timer事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
        
        /// 通知 Observers: 即将处理Source事件
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources)
        
        /// 处理Blocks
        __CFRunLoopDoBlocks(rl, rlm);
        
        /// 处理sources0
        Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
        
        /// 处理sources0返回为YES
        if (sourceHandledThisLoop) {
            /// 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
        }
        
        
        /// 判断有无端口消息(Source1)
        if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
            /// 处理消息
            goto handle_msg;
        }
        
        /// 通知 Observers: 即将进入休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
        __CFRunLoopSetSleeping(rl);
        
        /// 等待被唤醒
        __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
        
        
        // user callouts now OK again
        __CFRunLoopUnsetSleeping(rl);
        
        /// 通知 Observers: 被唤醒,结束休眠
        __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
        
    handle_msg:
        if (被Timer唤醒) {
            /// 处理Timers
            __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
        } else if (被GCD唤醒) {
            /// 处理gcd
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
        } else if (被Source1唤醒) {
            /// 被Source1唤醒,处理Source1
            __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
        }
        
        /// 处理block
        __CFRunLoopDoBlocks(rl, rlm);
        
        
        if (sourceHandledThisLoop && stopAfterHandle) {
            retVal = kCFRunLoopRunHandledSource;
        } else if (timeout_context->termTSR < mach_absolute_time()) {
            retVal = kCFRunLoopRunTimedOut;
        } else if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            retVal = kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            retVal = kCFRunLoopRunStopped;
        } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            retVal = kCFRunLoopRunFinished;
        }
        
    } while (0 == retVal);
    
    return retVal;
}

// main  dispatch queue
__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__

// __CFRunLoopDoObservers
__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

// __CFRunLoopDoBlocks
__CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__

// __CFRunLoopDoSources0
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__

// __CFRunLoopDoSource1
__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__

// __CFRunLoopDoTimers
__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__


 
 总结:

借用别人的一个图

相关资料:
官方文档 NSRunLoop Class Reference , CFRunLoop Reference.
CFRunLoopRef 的代码是开源的,你可以在这里http://opensource.apple.com/tarballs/CF/ 下载到整个 CoreFoundation 的源码来查看。 简单的话就把 CFRunLoop.c 、 CFRunLoop.h  拖到自己的demo 中去就可以了。

RunLoop 总结:
1、定义
    是一个处理事务的循环
    通过do while 实现,在需要的时候被唤醒,不需要的时候休眠
2、作用
    保持程序的持续运行
    处理APP中各种事件(触摸、定时器、perfromSelector)
    节省CPU,提升程序性能。需要时被唤醒,不需要处理任务是休眠
3、port、timer、observer(通知)、会带哦block 等都是依赖于RunLoop
4、RunLoop 与线程是一一对应的关系。
    在底层是根据线程进行判断,如果不存在线程就默认值主运行循环
    然后创建一个字典,创建运行循环。通过key value 的形式将 线程 和 运行循环进行绑定
    例如:dict[@"pthread_main_thread_np"] = mainLoop
5、
    RunLoop 与mode 是一对多的关系
    当前运行只能有一个mode,切换的话需要退出再切换。
    mode 与 item(source、timer、observer)的关系是一对多
6、子线程RunLoop 默认不启动,一般通过run 启动。
7、
    计时器(NSTimer)不是真正的实时的,当计时器未处于 RunLoop 当前监听的 mode 时,那么及时器是不会计时调度任务,只有RunLoop当前监听的mode 是计时器关联的mode 时,计时器才会开始任务。
    例如当timer添加到主运行循环的 defaultMode 中,tableView 滑动 会切换到 trackMode ,计时器就不会调度,当切换回 DefaultMode 时才会继续触发。
8、
    RunLoop会添加多个item。item需要依赖 mode 运行。每个mode 又有多个item(source、observer、timer)。
9、CFRunLoopRunSpecific
    1)、先判断modeName 找到对应 mode。因为run 是依赖mode去处理事务,如果没有找到 或者 mode 中没有注册任何事件,那么就此停止不进入循环。
    2)、如果当前的mode 和传过来mode 是不一样的说明有问题,或做一些处理(不重要不研究)
    3)、然后通知 observer : RunLoop 即将进入 loop
    4)、进入 __CFRunLoop 处理
    5)、最后通知 observer: RunLoop 即将退出
10、CFRunLoopRun
    1)、获取系统时间,用于判断超时
    2)、超时判断:1、<0 已经超时  2、< TIMER_INTERVAL_LIMIT  没有超时  3、永不超时。
    3)、如果超时,去release释放,并返回。
    超时和完成都会进行相应的标记返回
    ……11条总结
11、整理 CFRunLoopRunSpecific  、CFRunLoopRun
    1)、通知 Observer :即将进入 Loop           — Observer
    2)、通知 Observer : 将要处理Timer         — Observer     run
    3)、通知 Observer : 将要处理 Source0    — Observer     run
    4)、处理Source0                                          — Source0       run
    5)、如果有Source1,调到第9步                   — Source1       run      Source0(port)
    6)、通知 Observer: 线程将进入休眠          — Observer     run      Timer
    7)、休眠,等待唤醒                                                                 run
    8)、通知 Observer : 线程刚被唤醒             — Observer     run
    9)、处理唤醒时收到的消息,之后跳回2       — Timer  Source1     run
    10)、通知 Observer : 即将推出Loop          — Observer   

发布了104 篇原创文章 · 获赞 13 · 访问量 19万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/105054366