iOS底层-- RunLoop

作者: Engandend | 来源:发表于2020-09-11 20:09 被阅读0次

    手动目录

    • RunLoop 6大响应事件
    • RunLoop 与线程的关系
    • RunLoop状态监听
    • RunLoop 数据结构
    • RunLoop流程
      • 如何进行休眠的
    • RunLoop 与autoreleasePool
    • RunLoop 与GCD
    • RunLoop 应用
      • 线程保活

    什么是RunLoop?
    RunLoop 是循环运行,在程序运行过程中,循环做一些事情。
    RunLoop 是通过内部的运行循环,来对消息/事件进行管理的一个对象。

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

    RunLoop 6大响应事件

    • block: __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__
    • 调用Timer:__CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
    • 响应source0:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__
    • 响应source1:__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__
    • GCD主队列__CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__
    • observer源:比如通知__CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__

    当 RunLoop 进行回调时,一般都是通过一个很长的函数调用出去 (call out), 当你在你的代码中下断点调试时,通常能在调用栈上看到这些函数。下面是这几个函数的整理版本,如果你在调用栈中看到这些长函数名,在这里查找一下就能定位到具体的调用地点了:

    可结合地步的【流程图】来看

    {
        /// 1. 通知Observers,即将进入RunLoop
        /// 此处有Observer会创建AutoreleasePool: _objc_autoreleasePoolPush();
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopEntry);
        do {
     
            /// 2. 通知 Observers: 即将触发 Timer 回调。
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeTimers);
            /// 3. 通知 Observers: 即将触发 Source (非基于port的,Source0) 回调。
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeSources);
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
     
            /// 4. 触发 Source0 (非基于port的) 回调。
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__(source0);
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK__(block);
     
            /// 6. 通知Observers,即将进入休眠
            /// 此处有Observer释放并新建AutoreleasePool: _objc_autoreleasePoolPop(); _objc_autoreleasePoolPush();
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopBeforeWaiting);
     
            /// 7. sleep to wait msg.
            mach_msg() -> mach_msg_trap();
            
     
            /// 8. 通知Observers,线程被唤醒
            __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopAfterWaiting);
     
            /// 9. 如果是被Timer唤醒的,回调Timer
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(timer);
     
            /// 9. 如果是被dispatch唤醒的,执行所有调用 dispatch_async 等方法放入main queue 的 block
            __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(dispatched_block);
     
            /// 9. 如果如果Runloop是被 Source1 (基于port的) 的事件唤醒了,处理这个事件
            __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__(source1);
     
     
        } while (...);
     
        /// 10. 通知Observers,即将退出RunLoop
        /// 此处有Observer释放AutoreleasePool: _objc_autoreleasePoolPop();
        __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__(kCFRunLoopExit);
    }
    

    RunLoop 与线程的关系

    一一对应的关系。

    • 在内部,线程-runloop 在内部以key-value的形式存储在一个全局字典中 。
    • 当然,子线程中中,如果不主动去获取,runloop是没有的。主线程中的runloop是在main函数的 UIApplicationMain中创建的。

    NSRunLoop 是对 CFRunLoopRef 的包装,
    在代码中,一般有这几种写法

        NSRunLoop *mainLoop1    = [NSRunLoop mainRunLoop];
        NSRunLoop *currentLoop1 = [NSRunLoop currentRunLoop];
        CFRunLoopRef mainLoop2  = CFRunLoopGetMain();
        CFRunLoopRef runloop2 = CFRunLoopGetCurrent();
    

    底层都是C语言代码,在追踪源码的时候,要用CFRunLoopGetMain 或者CFRunLoopGetCurrent来追踪

    我们在源码中能找到这样的代码:(源码不是工程,创建一个空工程,将源码拖进去)

    // 追踪 CFRunLoopGetCurrent
    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK();
        CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
        if (rl) return rl;
        return _CFRunLoopGet0(pthread_self());
    }
    
    static CFMutableDictionaryRef __CFRunLoops = NULL;
    
    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());
            
        // 进行绑定 dict[@"pthread_main_thread_np"] = mainLoop
        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;
    }
    

    在源码中 我们看到,所有的runloop存在一个字典CFMutableDictionaryRef,
    字典中 以key-value 的形式存取,CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);

    类似于这样
    NSMutableDictionary *runloops = [NSMutableDictionary new];
    runloops[@"祝线程"] = mainRunloop;
    runloops[@"线程1"] = runloop1;
    runloops[@"线程2"] = runloop2;
    

    RunLoop状态监听

    RunLoop 有几种状态

    enum CFRunLoopActivity {
        kCFRunLoopEntry              = (1 << 0),    // 即将进入Loop   
        kCFRunLoopBeforeTimers      = (1 << 1),    // 即将处理 Timer        
        kCFRunLoopBeforeSources     = (1 << 2),    // 即将处理 Source  
        kCFRunLoopBeforeWaiting     = (1 << 5),    // 即将进入休眠     
        kCFRunLoopAfterWaiting      = (1 << 6),    // 刚从休眠中唤醒   
        kCFRunLoopExit               = (1 << 7),    // 即将退出Loop  
        kCFRunLoopAllActivities     = 0x0FFFFFFFU  // 包含上面所有状态  
    };
    

    RunLoop的状态是可以添加监听来看看是怎样的状态

    // 方法1:   指定 执行方法的 方式
    void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
    {
        switch (activity) {
            case kCFRunLoopEntry:
                NSLog(@"kCFRunLoopEntry");
                break;
           ......
            default:
                break;
        }
    }
    
    {
    //     创建Observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
        // 添加Observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放
        CFRelease(observer);
    }
    
    
    // 方法2:  block 方式
    {
        // 创建Observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry: {
                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                    NSLog(@"kCFRunLoopEntry - %@", mode);
                    CFRelease(mode);
                    break;
                }
                    
                case kCFRunLoopExit: {
                    CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                    NSLog(@"kCFRunLoopExit - %@", mode);
                    CFRelease(mode);
                    break;
                }
                    
                default:
                    break;
            }
        });
        // 添加Observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放
        CFRelease(observer);
    }
    

    RunLoop 数据结构

    追踪源码 CFRunLoop

    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;
    }; 
    
    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 */
    };
    

    __CFRunLoop 关键5个信息

    • _pthread:
      与之对应的线程
    • _commonModes:
    • _commonModeItems:
      在commonModes状态下运行的对象(例如Timer)
    • _currentMode:
      在当前loop下运行的mode(即使有多种mode,但同一时刻只能在一个mode下运行)
    • modes:
      运行的所有模式----- 类似于CFMutableSetRef< CFRunLoopModeRef >

    __CFRunLoopMode 关键的信息

    • _sources0
      触摸事件处理
      performSelector:onThread:withObject:waitUntilDone:
    • _sources1
      基于Port的线程间的通讯
      系统事件捕捉(比如点击事件,由source1捕捉,然后包装之后交给source0处理)
    • _observers
      监听RunLoop的状态
      UI刷新(在BeforeWaiting的时候刷新)
      AutoreleasePool
    • _timers
      NSTimer
      performSelector:withObject:、 afterDelay:

    RunLoopMode 类型

    • NSDefaultRunLoopMode
      App的默认Mode
    • UITrackingRunLoopMode
      滚动的时候处于这个mode
    • NSRunLoopCommonModes
      这是一个占位用的Mode,不是一种真正的Mode,它是上面2个mode的集合
    • UIInitializationRunLoopMode
      在刚启动App时进入的第一个Mode,启动完成后就不再使用
    • GSEventReceiveRunLoopMode
      接受系统事件的Mode,通常由系统去使用

    前三种mode 对外提供,后面的2中不能使用

    RunLoop 对应关系 RunLoop结构

    为什么 一个runloop里包含多个mode?
    不同mode中的 source0、source1、timer、observe 能分割开来,互不影响。

    RunLoop流程

    流程问题,从run开始
    (内部代码太多,贴一份精简的代码)

    // 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);
    }
    
    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;
    }
    
    • 1、observers 进入loop(entry)

    • 2、通知observer 即将处理Timer(BeforeTimers)

    • 3、通知observer即将处理Sources (BeforeSources )

    • 4、处理GCD

    • 5、处理source0

      • 5.1 如果source0 有GCD需要处理,就处理GCD
    • 6、判断有没有端口消息(source1),有的话 继续处理

    • 7、通知 observe即将进入休眠(BeforeWaiting),进入休眠之后进行事件监听。(会一直卡在这直到被其他唤醒)

    • 8、通知observe结束休眠(AfterWaiting)

      • 8.1、Timer唤醒 : 处理Timer
      • 8.2、GCD唤醒:处理GCD
      • 8.3、source1唤醒:处理Source1
    • 9、执行GCD

    • 10、根据之前的执行结果 决定loop是继续循环 还是退出

    • 11、通知observe 退出(exit )。

    流程图

    这里有一个点需要注意 :
    能够唤醒loop的有3个:TimerGCDSource1
    Source0不能直接唤醒loop
    source0是用户触摸事件,实际上是有系统(Source1)捕捉到点击事件,然后包装之后交给source0处理

    如何进行休眠的

    在用户层,是无法达到休眠(等待而不执行任何事物)的目的。通过内核去操作。

    以下内容摘录于:深入理解RunLoop
    mach_msg() 函数实际上是调用了一个 Mach 陷阱 (trap),即函数mach_msg_trap(),陷阱这个概念在 Mach 中等同于系统调用。当你在用户态调用 mach_msg_trap() 时会触发陷阱机制,切换到内核态;内核态中内核实现的 mach_msg() 函数会完成实际的工作,如下图:

    休眠的实现

    RunLoop 与autoreleasePool

    • 即将进入Loop(kCFRunLoopEntry),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其order是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
    • 准备进入休眠(kCFRunLoopBeforeWaiting),此时调用 _objc_autoreleasePoolPop() 对释放池里面的 对象进行release操作
    • 即将退出Loop(kCFRunLoopExit)此时调用 _objc_autoreleasePoolPop()释放自动释放池。这个 Observer的order是2147483647,确保池子释放在所有回调之后。

    RunLoop 与GCD

    RunLoop与GCD的关系与2点:
    1、在do..while 循环中,判断runloop有没有超时,用的是GCD的source_timer
    2、当回主线程,也就是 GCD使用main_queue的时候,会向runloop发送一个_MAIN_DISPATCH_QUEUE的消息来唤醒runloop。这也是为什么子线程不能刷UI的原因之一。

    • 子线程 的GCD不能做UI的刷新等操作。是因为在子线程中,无法唤醒runloop进行UI刷新。
      我们做以下 操作:
      - (void)task8 {
      dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            self.view.backgroundColor = [UIColor redColor];
            dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
                NSLog(@"1");
            });
        });
      
      // [[NSRunLoop currentRunLoop] run];
      }
      
      执行这段代码,虽然设置backgroundColor的时候,颜色没有发生变化,但是当dispatch_after执行,或者主动执行runloop run 操作 的时候,颜色成功的改变了。

    RunLoop 应用

    线程保活

    上面说了runloop的原理。
    那么就知道为什么有时候NSTimer不起效了。

    RunLoop还能干什么? --线程保活
    一般来说,我们在子线程里面执行完任务,子线程就会退出,但是,有时候我们需要在子线程多次执行任务,就需要线程一直不退出来节省消耗。
    我们可以这样做。
    ------向子线程中 添加一个port (相当于向runloop中 添加了一个 source1)保证runloop中一直有事件要处理。
    代码如下:

    @property (nonatomic, strong) JEThread *thread;
    @property (nonatomic, assign) BOOL stopped;
    
    {
      __weak typeof(self) weakSelf = self;
        _thread = [[JEThread alloc] initWithBlock:^{         // iOS 10.0
            [[NSRunLoop currentRunLoop ] addPort:[NSPort new] forMode:NSDefaultRunLoopMode];
    //            [[NSRunLoop currentRunLoop] run];         // 这个需要注意,内部会无限循环run, 不能手动停止
                while (weakSelf && !weakSelf.stopped) {
                    [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
                }
            NSLog(@"-------end-------");
        }];
        [self.thread start];
    }
    
    - (void)stop {
        if (!self.thread)  return;
        // 在子线程调用stop
        [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
    }
    
    // 用于停止子线程的RunLoop
    - (void)stopThread
    {
        // 设置标记为NO
        self.stopped = YES;
        
        // 停止RunLoop
        CFRunLoopStop(CFRunLoopGetCurrent());
        NSLog(@"%s %@", __func__, [NSThread currentThread]);
    
        self.thread = nil;
    }
    
    - (void)dealloc {
        [self stop];
        NSLog(@"vc dealloc");
    }
    
    
    // 在子线程里处理自己的事情
    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
        if (!self.thread)  return;
        [self performSelector:@selector(doSomeThings) onThread:self.thread withObject:nil waitUntilDone:NO];
    }
    - (void)doSomeThings {
        NSLog(@"1");
    }
    

    这里面有几个点需要注意:

    • 1、为什么没有用[runloop run] 而是用runMode:beforeDate?
      因为run 内部进行循环调用 runMode:beforeDate,我们需要自己打破这个循环,所以我们要进行自己的 while 条件循环。

    • 2、stop方法里面 的 waitUntilDone 必须为YES?
      考虑到可能会在dealloc里面调用stop,如果不等待,就会先释放了self,这个时候再进行 self.stopped 就是无效的。

    参考 文章:
    深入理解RunLoop 🔥🔥

    相关文章

      网友评论

        本文标题:iOS底层-- RunLoop

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