RunLoop

作者: 张_何 | 来源:发表于2021-02-20 14:13 被阅读0次

    runloop 的状态待完善

    相关面试题

    • 讲讲 RunLoop,项目中有用到吗?
      1、控制线程的生命周期(线程保活)
      2、解决 NSTimer 在滑动时停止工作的问题
      3、监控应用卡帧
      4 、性能优化
    • runloop内部实现逻辑?
    • runloop和线程的关系?
      线程与 runloop 一一对应,主线程的 runloop 默认开启,子线程的 runloop 在第一次获取的时候开启,runloop 在线程结束时销毁,线程和 runloop 存放在一个全局的字典中,该字典以线程 key,runloop 作为 value.
    • timer 与 runloop 的关系?
      timer 需要添加到 runloop 中才能工作,runloop 中有一个CFRunLoopTimerRef 类型的事件源专门用来出来 timer 事件.
    • 程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?
      将 timer 添加到 runloop 的 commonModel 模式下
    • runloop 是怎么响应用户操作的, 具体流程是什么样的?
    • 说说runLoop的几种状态
    • runloop的mode作用是什么?

    什么是 RunLoop

    • RunLoop 顾名思义就是在运行过程中循环做一些事情
    • 应用范畴:
      1、定时器(Timer)、PerformSelector
      2、GCD Async Main Queue
      3、事件响应、手势识别、界面刷新
      4、网络请求
      5、AutoreleasePool

    RunLoop的作用

    1、保持程序的持续运行
    2、处理App中的各种事件(比如触摸事件、定时器事件等)
    3、节省CPU资源,提高程序性能:该做事时做事,该休息时休息

    • 如果没有 RunLoop 程序执行完就会退出,例如下面的代码,程序在执行完main 函数后就会退出。
    int main (int argc, const char * argv[]){
      @autoreleasepool{
        NSLog(@"hello world");
      }
    return 0;
    }
    
    • 但是我们通常看到的ios 程序运行在手机上后就一直处在运行状态等待用户的响应,它是怎么做到的呢?其实是因为ios 应用程序在 UIApplicationMain函数中帮我们开启了一个 runloop,runloop 保持我们的 ios 程序持续运行,下面是一个 ios 的 main 函数:
    int main(int argc, char *argv[]){
      @autoreleasepool{
        return UIApplicationMain(argc, argv,nil,NSStringFromClass([AppDelegate class]));
      }
    }
    
    • 那么 runLoop 是怎么实现的呢?
      下面用伪代码来实现以下
    int main(int argc, char *argc[]){
      int retVal = 0;
      do{
        // 睡眠中等待消息
        int message = sleep_and_wait();
        // 处理消息,处理完消息返回 0,继续 do...while 循环
        retVal = process_message(message);
      } while(0 == retVal);
      return 0;
    }
    

    RunLoop 对象

    • iOS中有2套API来访问和使用RunLoop
      1、Foundation:NSRunLoop
      2、Core Foundation:CFRunLoopRef
    • NSRunLoop和CFRunLoopRef都代表着RunLoop对象
    • NSRunLoop是基于CFRunLoopRef的一层OC包装
    • CFRunLoopRef是开源的
      https://opensource.apple.com/tarballs/CF/

    RunLoop 与线程

    • 每条线程都有唯一的一个与之对应的RunLoop对象
    • RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
    • 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
    • RunLoop会在线程结束时销毁
      1、获取 Runloop的方式:
      [NSRunLoop currentRunLoop];获取当前线程的 RunLoop 对象
      [NSRunLoop mainRunLoop];获取主线程的 RunLoop 对象
      或者
      CFRunLoopGetCurrent();获得当前线程的 RunLoop 对象
      CFRunLoopGetMain()获得主线程的 Runloop 对象
    • 下面我们通过源码来验证上面的结论:
      由于 CFRunLoop 是开源的我们可以在源码中找到CFRunLoopGetCurrent函数
    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK();
        CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
        if (rl) return rl;
        return _CFRunLoopGet0(pthread_self());
    }
    

    上面我们看到其返回的是一个_CFRunLoopGet0函数

    CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_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);
        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);
            __CFLock(&loopsLock);
        }
    // 这里我们可以看到 loop 对象是从CFDictionaryGetValue这个字典中取出来的,而且是以线程指针作为 key,runloop 对象作为 value 的,
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
    // 这里如果从字典中去到的loop 对象是空,那么就会创建一个新的线程对象
        if (!loop) { 
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
    // 这里将新的 newLoop 对象赋值给 loop,并保存在字典中
        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;
    }
    

    RunLoop 相关的类

    • Core Foundation中关于RunLoop的5个类
    CFRunLoopRef :
    • CFRunLoopRef其实是一个__CFRunLoopRef类型的结构体,其有两个比较中要的变量CFRunLoopModeRef _currentMode; CFMutableSetRef _modes;
      _currentMode存放的是CFRunLoopModeRef类型的当前mode
      _modes存放的是当前RunLoop对象中所有的CFRunLoopModeRef类型的mode集合
      _commonModes 中存放的是所有标记为 commonModel 的集合,例如kCFRunLoopDefaultModeUITrackingRunLoopMode
      _commonModeItems中存放的是所有添加到kCFRunLoopCommonModes的所有对象,
      源码如下:
    typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoop * CFRunLoopRef;
    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;
        CFMutableSetRef _commonModeItems;
        CFRunLoopModeRef _currentMode;
        CFMutableSetRef _modes;
        struct _block_item *_blocks_head;
        struct _block_item *_blocks_tail;
        CFAbsoluteTime _runTime;
        CFAbsoluteTime _sleepTime;
        CFTypeRef _counterpart;
    };
    

    其结构如下:


    image.png
    CFRunLoopModeRef
    • 它其实是一个__CFRunLoopMode类型的结构体,其有四个比较重要的变量CFMutableSetRef _sources0; CFMutableSetRef _sources1; CFMutableArrayRef _observers; CFMutableArrayRef _timers;
      _sources0_sources1中存放的是CFRunLoopSourceRef类型的变量;
      _observers中存放的是CFRunLoopObserverRef类型的变量;
      _timers中存放的是CFRunLoopTimerRef类型的变量;
      源码如下:
    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;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
        CFMutableDictionaryRef _portToV1SourceMap;
        __CFPortSet _portSet;
        CFIndex _observerMask;
    #if USE_DISPATCH_SOURCE_FOR_TIMERS
        dispatch_source_t _timerSource;
        dispatch_queue_t _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 */
    };
    
    • CFRunLoopModeRef 代表RunLoop的运行模式,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
      ,RunLoop启动时只能选择其中一个Mode,作为currentMode,如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入,不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响,如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出。
    目前已知的Mode有5种
    • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
    • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
    • UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
    • GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到.
    • kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode.添加到该模式下的事件源可以在所有标记为 commonMode的mode(kCFRunLoopDefaultMode、UITrackingRunLoopMode) 下运行
    CFRunLoopSourceRef
    • _modes中的_sources0和_sources1的类型;
    • _sources0主要处理,触摸时间和performSelector:onThread:类型的事件
      下面我们用源码验证一下,首先验证触摸事件的
      我们新建一个viewController中添加一个touchesBegan:方法,并在NSLog处打上断点,
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        NSLog(@"touchesBegan");
    }
    

    点击屏幕触发断点,然后我们在控制台执行bt,得到函数调用栈信息如下:

    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
      * frame #0: 0x0000000105516efd RunLoop`-[ViewController touchesBegan:withEvent:](self=0x00007f8b9e705a60, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x0000600001884d80) at ViewController.m:25:5
        frame #1: 0x00007fff48bf59c6 UIKitCore`forwardTouchMethod + 323
        frame #2: 0x00007fff48bf5872 UIKitCore`-[UIResponder touchesBegan:withEvent:] + 49
        frame #3: 0x00007fff48c0478f UIKitCore`-[UIWindow _sendTouchesForEvent:] + 622
        frame #4: 0x00007fff48c067f5 UIKitCore`-[UIWindow sendEvent:] + 4501
        frame #5: 0x00007fff48be0c39 UIKitCore`-[UIApplication sendEvent:] + 356
        frame #6: 0x00007fff48c6b096 UIKitCore`__dispatchPreprocessedEventFromEventQueue + 7328
        frame #7: 0x00007fff48c6e262 UIKitCore`__handleEventQueueInternal + 6565
        frame #8: 0x00007fff48c64dcb UIKitCore`__handleHIDEventFetcherDrain + 88
        frame #9: 0x00007fff23d9deb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
        frame #10: 0x00007fff23d9dddc CoreFoundation`__CFRunLoopDoSource0 + 76
        frame #11: 0x00007fff23d9d5b4 CoreFoundation`__CFRunLoopDoSources0 + 180
        frame #12: 0x00007fff23d981ae CoreFoundation`__CFRunLoopRun + 974
        frame #13: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
        frame #14: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
        frame #15: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
        frame #16: 0x00000001055171c2 RunLoop`main(argc=1, argv=0x00007ffeea6e7cb8) at main.m:18:12
        frame #17: 0x00007fff519521fd libdyld.dylib`start + 1
        frame #18: 0x00007fff519521fd libdyld.dylib`start + 1
    (lldb) 
    

    在frame #9中我们看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ 可以验证该事件源来自source0。

    下面我们验证performSelector:onThread:类型的,在viewDidLoad方法中加入如下代码,并在test方法内打上断点

        dispatch_async(dispatch_get_global_queue(0,0), ^{
             [self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:true];
        });
        
    -(void)test{
        NSLog(@"test");
    }
    

    触发断点,在控制台执行bt,得到如下函数堆栈信息:

    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
      * frame #0: 0x0000000102a14e27 RunLoop`-[ViewController test](self=0x00007fcaab50b2e0, _cmd="test") at ViewController.m:28:5
        frame #1: 0x00007fff2593af42 Foundation`__NSThreadPerformPerform + 209
        frame #2: 0x00007fff23d9deb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
        frame #3: 0x00007fff23d9dddc CoreFoundation`__CFRunLoopDoSource0 + 76
        frame #4: 0x00007fff23d9d60c CoreFoundation`__CFRunLoopDoSources0 + 268
        frame #5: 0x00007fff23d981ae CoreFoundation`__CFRunLoopRun + 974
        frame #6: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
        frame #7: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
        frame #8: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
        frame #9: 0x0000000102a15152 RunLoop`main(argc=1, argv=0x00007ffeed1e9cb8) at main.m:18:12
        frame #10: 0x00007fff519521fd libdyld.dylib`start + 1
        frame #11: 0x00007fff519521fd libdyld.dylib`start + 1
    (lldb) 
    

    从frame #2中可以看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__验证是source0。

    • 那么source1是干嘛的呢?source1主要是用来处理基于Port的线程间通信 和 系统事件捕捉的

    • source0 和 source1 有什么区别呢?
      Source1 :基于mach_Port的,来自系统内核或者其他进程或线程的事件,可以主动唤醒休眠中的RunLoop(iOS里进程间通信开发过程中我们一般不主动使用)。mach_port大家就理解成进程间相互发送消息的一种机制就好, 比如屏幕点击, 网络数据的传输都会触发sourse1。Source0 :非基于Port的 处理事件,什么叫非基于Port的呢?就是说你这个消息不是其他进程或者内核直接发送给你的。一般是APP内部的事件, 比如hitTest:withEvent的处理, performSelectors的事件。简单举个例子:一个APP在前台静止着,此时,用户用手指点击了一下APP界面,那么过程就是下面这样的:
      我们触摸屏幕,先摸到硬件(屏幕),屏幕表面的事件会被IOKit先包装成Event,通过mach_Port传给正在活跃的APP , Event先告诉source1(mach_port),source1唤醒RunLoop, 然后将事件Event分发给source0,然后由source0来处理。
      如果没有事件,也没有timer,则runloop就会睡眠, 如果有,则runloop就会被唤醒,然后跑一圈。
      我们可以这样理解source1是系统事件,source0是应用层事件,source1接收到事件后将事件分发给source0来处理

    CFRunLoopTimerRef
    • _modes中的_timers的类型,主要处理定时器事件
      我们在viewDidLoad中添加下面源码,并在test方法内打上断点,
    [NSTimer scheduledTimerWithTimeInterval:3 repeats:true block:^(NSTimer * _Nonnull timer) {
            [self test];
        }];
    

    触发断点,在终端中执行bt,得到如下信息:

    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 2.1
      * frame #0: 0x0000000102b44e27 RunLoop`-[ViewController test](self=0x00007fcc0bc0a490, _cmd="test") at ViewController.m:32:5
        frame #1: 0x0000000102b44d7b RunLoop`__29-[ViewController viewDidLoad]_block_invoke(.block_descriptor=0x00006000018c02d0, timer=0x00006000023a8900) at ViewController.m:25:9
        frame #2: 0x00007fff2594462e Foundation`__NSFireTimer + 72
        frame #3: 0x00007fff23d9e634 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__ + 20
        frame #4: 0x00007fff23d9e2ce CoreFoundation`__CFRunLoopDoTimer + 1038
        frame #5: 0x00007fff23d9d92a CoreFoundation`__CFRunLoopDoTimers + 282
        frame #6: 0x00007fff23d9857e CoreFoundation`__CFRunLoopRun + 1950
        frame #7: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
        frame #8: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
        frame #9: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
        frame #10: 0x0000000102b45152 RunLoop`main(argc=1, argv=0x00007ffeed0b9cb8) at main.m:18:12
        frame #11: 0x00007fff519521fd libdyld.dylib`start + 1
        frame #12: 0x00007fff519521fd libdyld.dylib`start + 1
    (lldb) 
    

    在frame #3:行中我们看到__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__验证是处理定时器事件的。

    CFRunLoopObserverRef
    • 用来监听RunLoop的状态
    • 对应状态如下:

    RunLoop是怎样的一个循环

    • 流程如下:
    01、通知Observers:进入Loop
    02、通知Observers:即将处理Timers
    03、通知Observers:即将处理Sources
    04、处理Blocks
    05、处理Source0(可能会再次处理Blocks)
    06、如果存在Source1,就跳转到第8步
    07、通知Observers:开始休眠(等待消息唤醒)
    08、通知Observers:结束休眠(被某个消息唤醒)
    01> 处理Timer
    02> 处理GCD Async To Main Queue
    03> 处理Source1
    09、处理Blocks
    10、根据前面的执行结果,决定如何操作
    01> 回到第02步
    02> 退出Loop
    11、通知Observers:退出Loop
    
    • 上面我们在执行断点时在终端使用bt查看函数调用栈的时候都会看到CoreFoundation __CFRunLoopRun这个函数,其实这个就是Runloop启动的入口。在源码CFRunLoop.c中可以找到CFRunLoopRun函数,源码如下:
    void CFRunLoopRun(void) {   /* DOES CALLOUT */
        int32_t result;
        do {
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
    

    可以看到源码中就调用了CFRunLoopRunSpecific这个函数,源码如下:

    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
        CHECK_FOR_FORK();
        if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished;
        __CFRunLoopLock(rl);
        CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false);
        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);
        CFRunLoopModeRef previousMode = rl->_currentMode;
        rl->_currentMode = currentMode;
        int32_t result = kCFRunLoopRunFinished;
    // 通知Observers:进入runloop
        if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    // 进行runloop 的核心逻辑
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    // 通知Observers:退出runloop
        if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
            __CFRunLoopModeUnlock(currentMode);
            __CFRunLoopPopPerRunData(rl, previousPerRun);
        rl->_currentMode = previousMode;
        __CFRunLoopUnlock(rl);
        return result;
    }
    

    通过源码看到返回的result是通过__CFRunLoopRun函数获取的,__CFRunLoopRun函数源码比较多,下面是提取出来的核心代码

    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
        uint64_t startTSR = mach_absolute_time();
    // 先判断是否是stopped状态,如果是返回stopped的状态
        if (__CFRunLoopIsStopped(rl)) {
            __CFRunLoopUnsetStopped(rl);
            return kCFRunLoopRunStopped;
        } else if (rlm->_stopped) {
            rlm->_stopped = false;
            return kCFRunLoopRunStopped;
        }
    
        int32_t retVal = 0;
        do {
            // 通知Observers 即将处理Timers
            if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            // 通知Observers 即将处理Sources
            if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
            // 处理Blocks,是NSRunLoop 中的 performBlock:函数中的block
            __CFRunLoopDoBlocks(rl, rlm);
    
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            if (sourceHandledThisLoop) {、
                // 处理Blocks,是NSRunLoop 中的 performBlock:函数中的block
                __CFRunLoopDoBlocks(rl, rlm);
            }
    
            Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
    
            //如果有Sources1,就跳转打handle_msg标记处
            if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
                msg = (mach_msg_header_t *)msg_buffer;
                if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                    goto handle_msg;
                }
            }
    
            // 通知Observers即将休眠
            if (!poll && (rlm->_observerMask & kCFRunLoopBeforeWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            // 进入休眠,等待其他消息唤醒
            __CFRunLoopSetSleeping(rl);
        // do not do any user callouts after this point (after notifying of sleeping)
    
            // Must push the local-to-this-activation ports in on every loop
            // iteration, as this mode could be run re-entrantly and we don't
            // want these ports to get serviced.
    
            __CFPortSetInsert(dispatchPort, waitSet);
            // 休眠后进去do...while(1)的死循环,循环里等待唤醒的消息,如果被唤醒执行break跳出死循环
            do {
                if (kCFUseCollectableAllocator) {
                    // objc_clear_stack(0);
                    // <rdar://problem/16393959>
                    memset(msg_buffer, 0, sizeof(msg_buffer));
                }
                msg = (mach_msg_header_t *)msg_buffer;
                
                __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
                
                if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                    // Drain the internal queue. If one of the callout blocks sets the timerFired flag, break out and service the timer.
                    while (_dispatch_runloop_root_queue_perform_4CF(rlm->_queue));
                    if (rlm->_timerFired) {
                        // Leave livePort as the queue port, and service timers below
                        rlm->_timerFired = false;
                        break;
                    } else {
                        if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
                    }
                } else {
                    break;
                }
            } while (1);
            // 醒来
            __CFPortSetRemove(dispatchPort, waitSet);
            __CFRunLoopSetIgnoreWakeUps(rl);
            __CFRunLoopUnsetSleeping(rl);
            // 通知Observers 已经被唤醒
            if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
    
            handle_msg:;
            __CFRunLoopSetIgnoreWakeUps(rl);
    
            if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) { // 被timer唤醒,处理Timer
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    // Re-arm the next timer
                    __CFArmNextTimerInMode(rlm, rl);
                }
            }
            else if (livePort == dispatchPort) { // 被GCD 唤醒,处理GCD
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
    
            } else { // 被sources1唤醒,处理Sources1
                if (rls) {
                    sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
            }
            // 执行Blocks
            __CFRunLoopDoBlocks(rl, rlm);
            
            // 根据之前的执行结果,来决定怎么做,如果retVal被标记为非零,当前循环结束,返回retVal,CFRunLoopRunSpecific中通知observers退出runloop,如果是Stopped或Finished则外层CFRunLoopRun中的do...while循环结束,代码执行完毕,线程结束
            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);
    
        if (timeout_timer) {
            dispatch_source_cancel(timeout_timer);
            dispatch_release(timeout_timer);
        } else {
            free(timeout_context);
        }
    
        return retVal;
    }
    
    
    • GCD 如果是异步回到主线程的话是由runloop处理的
        dispatch_async(dispatch_get_global_queue(0, 0), ^{
            NSLog(@"111-----%@",[NSThread currentThread]);
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"222-----%@",[NSThread currentThread]);
            });
        });
    

    在NSLog(@"222-----%@",[NSThread currentThread]);处断点,出发断点后,终端执行bt获得函数调用栈如下

    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
      * frame #0: 0x000000010f21dd70 RunLoop`__29-[ViewController viewDidLoad]_block_invoke_2(.block_descriptor=0x000000010f2200a0) at ViewController.m:31:33
        frame #1: 0x000000010f487f11 libdispatch.dylib`_dispatch_call_block_and_release + 12
        frame #2: 0x000000010f488e8e libdispatch.dylib`_dispatch_client_callout + 8
        frame #3: 0x000000010f496d97 libdispatch.dylib`_dispatch_main_queue_callback_4CF + 1149
        frame #4: 0x00007fff23d9da89 CoreFoundation`__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
        frame #5: 0x00007fff23d985d9 CoreFoundation`__CFRunLoopRun + 2041
        frame #6: 0x00007fff23d97ac4 CoreFoundation`CFRunLoopRunSpecific + 404
        frame #7: 0x00007fff38b2fc1a GraphicsServices`GSEventRunModal + 139
        frame #8: 0x00007fff48bc7f80 UIKitCore`UIApplicationMain + 1605
        frame #9: 0x000000010f21e102 RunLoop`main(argc=1, argv=0x00007ffee09e1cb8) at main.m:18:12
        frame #10: 0x00007fff519521fd libdyld.dylib`start + 1
        frame #11: 0x00007fff519521fd libdyld.dylib`start + 1
    

    通过函数调用栈看到其是通过CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE函数来处理的

    runloop示意图.png
    线程保活

    线程一旦执行完线程内的任务就会死亡,如果不想让线程在执行完任务之后死亡,就需要给线程的 runloop 的 mode 中添加source0、source1、timer、observer如果 runloop 中有四个中的任意一种就不会退出,runloop 不退出线程内的任务就没执行完,就不会销毁,我们可以给 runloop 添加一种事件源,让其不退出

    @interface ZWThread : NSThread
    
    @end
    @implementation ZWThread
    - (void)dealloc {
        NSLog(@"%s", __func__);
    }
    
    @end
    @interface ViewController ()
    @property (strong, nonatomic) ZWThread *thread;
    @property (assign, nonatomic, getter=isStoped) BOOL stopped;
    @end
    
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        __weak typeof(self) weakSelf = self;
        self.stopped = NO;
        self.thread = [[ZWThread alloc] initWithBlock:^{
            NSLog(@"%@----begin----", [NSThread currentThread]);
            // 往RunLoop里面添加Source\Timer\Observer, 这样 runloop 不会立即退出
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
            while (!weakSelf.isStoped) { 
              // 这里运行 runloop,其实是"阻塞"线程,当 runloop 接收到事件时去处理事件,处理完了之后就休眠,当收到下次事件时重新判断 while 的条件是否成立,如果成立,继续执行循环体,如果不成立跳出 while 循环
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
            // 只有当 runloop stop 的时候才会走到这里打印,没有 stop 的话,就一直在 while 循环体内处理事件和休眠.
            NSLog(@"%@----end----", [NSThread currentThread]);
        }];
        [self.thread start];
    }
    
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    //    if (!self.thread) return;
        [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
    }
    
    // 子线程需要执行的任务
    - (void)test{
        NSLog(@"after %s %@", __func__, [NSThread currentThread]);
    }
    
    - (IBAction)stop {
        if (!self.thread) return;
        // 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
        [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
    }
    
    // 用于停止子线程的RunLoop
    - (void)stopThread{
        // 设置标记为YES
        self.stopped = YES;
        
        // 停止RunLoop
        CFRunLoopStop(CFRunLoopGetCurrent());
        NSLog(@"%s %@", __func__, [NSThread currentThread]);
        
        // 清空线程
        self.thread = nil;
    }
    
    - (void)dealloc{
        NSLog(@"%s", __func__);
        
        [self stop];
    }
    

    相关文章

      网友评论

          本文标题:RunLoop

          本文链接:https://www.haomeiwen.com/subject/wgxjzktx.html