美文网首页
Runloop相关阅读笔记

Runloop相关阅读笔记

作者: MichealXXX | 来源:发表于2019-09-26 11:27 被阅读0次

    一.Runloop是什么

    通俗点来说,我们有一个线程,当我们需要它处理事件时,它要随时启动,我们不需要它时,它要随时待命,如果我们每次运行完毕之后这个线程直接关闭了,下一次运行需要再重新创建一个新的线程,那无疑会大大的消耗CPU的性能。因此我们就需要一种机制,让线程随时待命,执行任务完毕进入休眠状态,等待下一次唤醒,在macOSiOS系统中,就是通过Runloop来对线程进行管理的

    二.Runloop的基本作用

    1.保证程序的持续运行,程序一启动就会创建主线程,主线程一开始执行就会创建对应的Runloop,保证线程不会被销毁,使其平稳运行。

    2.处理APP中的各种事件,比如我们的触摸事件,定时器事件,selector事件。

    3.节省CPU资源,提高程序性能,当程序运行起来的时候,线程没有接收到任何任务,其对应的Runloop就会告知CPU,我现在没事可做,我要去休息,当有事件产生时,需要这个线程去执行,Runloop就会立刻告之其所对应的线程去执行任务。

    Runloop运行原理
    从图中我们可以看到,当有事件产生,或者定时器方法需要执行时,Runloop就会将对应的任务安排给相应的处理方去执行。

    三.Runloop基本源码

    我们先来看一下Runloop的基本执行源码

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

    这段源码就是一个非常简单的do-while循环,CFRunLoopRun会根据result来判断是否要继续执行下去,当result停止或者结束时,Runloop便会跳出循环,否则就会继续执行。

    四.Runloop对象

    RunloopCocoa Foundation中是NSRunLoop对象,而在Core Foundation中则是 CFRunLoopRef对象,我们解读的源码是CFRunLoopRefRunloop的代码是开源的,可以前往苹果官网的地址进行下载:https://opensource.apple.com/tarballs/

    如何获取Runloop对象呢?苹果是不允许直接创建Runloop对象的,但是给我们提供了相应的方法获取线程的Runloop对象

    Foundation
    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    [NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
    
    Core Foundation
    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    CFRunLoopGetMain(); // 获得主线程的RunLoop对象
    

    五.线程与Runloop的关系

    以前苹果的多线程有两种pthread_tNSThreadpthread_tNSThread是一一对应的。比如,你可以通过 pthread_main_thread_np()[NSThread mainThread]来获取主线程;也可以通过pthread_self()[NSThread currentThread]来获取当前线程。CFRunLoop是基于pthread来管理的。我们先看一下Runloop的源码。

    CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
        }
    
        __CFLock(&loopsLock);
        //程序第一次进来判断是否存在runloop
        if (!__CFRunLoops) {
            //不存在runloop
            __CFUnlock(&loopsLock);
        //创建一个字典,用于管理保存线程以及其对应的runloop
        CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
        //使用主线程创建一个主循环(mainLoop)
        CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
        //为字典赋值,主线程为字典的key,runloop为字典的value
        CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
        if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
            CFRelease(dict);
        }
        CFRelease(mainLoop);
            __CFLock(&loopsLock);
        }
    
        //从字典中获取runloop,使用传进来的线程作为key来获取
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
        //如果没取到runloop
        if (!loop) {
        //新建一个runloop
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            //新建runloop成功,将runloop存入字典
            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线程和Runloop会保存在一个全局字典里系统会自动创建主线程的Runloop子线程的Runloop则需要手动创建,通过[NSRunLoop currentRunLoop]Runloop在获取时创建,在线程结束时销毁

    六.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;
        CFMutableSetRef _commonModeItems;
        CFRunLoopModeRef _currentMode;
        CFMutableSetRef _modes;
        struct _block_item *_blocks_head;
        struct _block_item *_blocks_tail;
        CFAbsoluteTime _runTime;
        CFAbsoluteTime _sleepTime;
        CFTypeRef _counterpart;
    };
    

    其实很多我们平时难以理解的概念,一旦进入到结构体层面去分析就会变得很好理解,比如Block原理,分类为什么不能添加属性等问题,去看一下相关源码立刻就会恍然大悟,因此我个人在学习的时候也很热衷于阅读相关源码,那么现在就让我们来看一下Runloop的结构体。

    先来看一下关键的成员变量

    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
    

    CFRunLoopModeRef其实是指向__CFRunLoopMode结构体的指针,我们再看一下CFRunLoopMode的结构体

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

    照例筛选出最关键的几个成员变量

    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
    

    Runloop在进行运行的时候是会选择对应模式的,也就是__CFRunLoopMode,每个Mode又有对应的source0/source1/observers/timers,每次Runloop运行的时候只能选择一个Mode作为currentMode

    六.source0/source1/observers/timers

    1.source0:处理的是App内部的事件、App自己负责管理,如按钮点击事件等
    2.source1:由RunLoop和内核管理,Mach Port驱动,如CFMachPort、CFMessagePort

    我们可以通过点击事件验证一下

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
        UITouch * touch = touches.anyObject;//获取触摸对象
        NSLog(@"%@",@(touch.tapCount));//短时间内的点击次数
    }
    

    在点击方法中打断点,然后在控制台输入bt即可看到完整的堆栈信息

    source0的验证

    同样的performSelector也会触发source0,我们用代码验证一下

    [self performSelectorOnMainThread:@selector(test) withObject:nil waitUntilDone:YES];
    

    在test方法中打断点,控制台输入bt查看堆栈信息


    performSelector验证

    再来看一下timer的验证,调用NSTimer方法

    [NSTimer scheduledTimerWithTimeInterval:3.0 repeats:NO block:^(NSTimer * _Nonnull timer) {
        NSLog(@"NSTimer ---- timer调用了");
    }];
    

    控制台验证


    NSTimer验证

    Observer:告知外界RunLoop状态的更改

    七.Runloop相关类以及作用

    Runloop的.h文件中我们可以看到,声明了四个类

    typedef struct __CFRunLoop * CFRunLoopRef;
    
    typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
    
    typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
    
    typedef struct CF_BRIDGED_MUTABLE_TYPE(NSTimer) __CFRunLoopTimer * CFRunLoopTimerRef;
    

    CFRunloop中又包含有一个关键的Mode

    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    

    这五个类就是Runloop实现的最关键的类,我们分别来说一下他们的作用

    1. CFRunLoopModeRef,这个代表了Runloop的运行模式,一个Runloop包含若干个Mode,每个Mode又包含若干Source,Timer,Observer,每次Runloop启动时,只能指定其中一个Mode,这个Mode被称作 CurrentMode,如果需要切换Mode,只能退出RunLoop,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source、Timer、Observer,让其互不影响。如果Mode里没有任何Source0/Source1/Timer/ObserverRunLoop会立马退出。
    CFRunLoopModeRef示意图

    一个Mode可以有多个Source,Observer,Timer。但是必须有一个Source或者Timer,不然Mode为空,Runloop会直接退出。

    系统默认注册了5个Mode

    1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
    2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
    3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
    4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
    5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
    

    有一个老生常谈的问题,我们使用NSTimer定时器,每隔一段时间执行一些事件,在这个过程中我们滑动ScrollView,定时器是会收到影响的。

    因为如果我们在主线程使用定时器,此时RunLoop的ModekCFRunLoopDefaultMode,即定时器属于kCFRunLoopDefaultMode,那么此时我们滑动ScrollView时,RunLoopMode会切换到UITrackingRunLoopMode,因此在主线程的定时器就不在管用了,调用的方法也就不再执行了,当我们停止滑动时,RunLoop的Mode切换回kCFRunLoopDefaultMode,所以NSTimer就又管用了。

    如果我们希望在滑动界面时,计时器仍然有效,我们指定Runloop的模式为kCFRunLoopCommonModes即可,因为kCFRunLoopCommonModes包含kCFRunLoopDefaultMode,kCFRunLoopDefaultMode

    1. CFRunLoopObserverRef是用来观察Runloop运行状态的类,看一下它的相关代码
    -(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
    {
         //创建监听者
         /*
         第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配
         第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态
         第三个参数 Boolean repeats:YES:持续监听 NO:不持续
         第四个参数 CFIndex order:优先级,一般填0即可
         第五个参数 :回调 两个参数observer:监听者 activity:监听的事件
         */
         /*
         所有事件
         typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
         kCFRunLoopEntry = (1UL << 0),   //   即将进入RunLoop
         kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
         kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
         kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
         kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
         kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
         kCFRunLoopAllActivities = 0x0FFFFFFFU
         };
         */
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"RunLoop进入");
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"RunLoop要处理Timers了");
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"RunLoop要处理Sources了");
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"RunLoop要休息了");
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"RunLoop醒来了");
                    break;
                case kCFRunLoopExit:
                    NSLog(@"RunLoop退出了");
                    break;
                    
                default:
                    break;
            }
        });
        
        // 给RunLoop添加监听者
        /*
         第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop
         第二个参数 CFRunLoopObserverRef observer 监听者
         第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态
         */
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
         /*
         CF的内存管理(Core Foundation)
         凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
         GCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了
         */
        CFRelease(observer);
    }
    

    我们来看一下控制台的输出


    Runloop流程

    可以看到Observer可以监听Runloop的运行流程。

    八.Runloop的处理逻辑

    先来看看Runloop的简化版源码,我们分成几个部分,一步一步的看

    //这个是Runloop的外部函数,内部会调用底层的CFRunLoopRunSpecific方法
    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方法

    // 经过精简的 CFRunLoopRunSpecific 函数代码,其内部会调用__CFRunLoopRun函数
    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {     /* DOES CALLOUT */
    
        // 通知Observers : 进入Loop
        // __CFRunLoopDoObservers内部会调用 __CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION__
    函数
        if (currentMode->_observerMask & kCFRunLoopEntry ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
        
        // 核心的Loop逻辑
        result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
        
        // 通知Observers : 退出Loop
        if (currentMode->_observerMask & kCFRunLoopExit ) __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    
        return result;
    }
    

    CFRunLoopRunSpecific函数中最关键的__CFRunLoopRun函数源码

    // 精简后的 __CFRunLoopRun函数,保留了主要代码
    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
        int32_t retVal = 0;
        do {
            // 通知Observers:即将处理Timers
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); 
            
            // 通知Observers:即将处理Sources
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
            
            // 处理Blocks
            __CFRunLoopDoBlocks(rl, rlm);
            
            // 处理Sources0
            if (__CFRunLoopDoSources0(rl, rlm, stopAfterHandle)) {
                // 处理Blocks
                __CFRunLoopDoBlocks(rl, rlm);
            }
            
            // 如果有Sources1,就跳转到handle_msg标记处
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                goto handle_msg;
            }
            
            // 通知Observers:即将休眠
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            
            // 进入休眠,等待其他消息唤醒
            __CFRunLoopSetSleeping(rl);
            __CFPortSetInsert(dispatchPort, waitSet);
            do {
                __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            } while (1);
            
            // 醒来
            __CFPortSetRemove(dispatchPort, waitSet);
            __CFRunLoopUnsetSleeping(rl);
            
            // 通知Observers:已经唤醒
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
            
        handle_msg: // 看看是谁唤醒了RunLoop,进行相应的处理
            if (被Timer唤醒的) {
                // 处理Timer
                __CFRunLoopDoTimers(rl, rlm, mach_absolute_time());
            }
            else if (被GCD唤醒的) {
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            } else { // 被Sources1唤醒的
                __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
            }
            
            // 执行Blocks
            __CFRunLoopDoBlocks(rl, rlm);
            
            // 根据之前的执行结果,来决定怎么做,为retVal赋相应的值
            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;
    }
    
    再附上一张流程图 RunLoop流程图

    九.Runloop的应用

    (1)AFNetworking:在子线程中的任务都执行完毕之后,子线程就会被销毁,但是我们遇到一些情况,希望这个线程在程序运行的过程中一直存在,就比如AFN,网络在运行期间当然要一直存在,因此AFNetworking中就使用了一个方式,使线程一直保持存在状态。

    + (void)networkRequestThreadEntryPoint:(id)__unused object {
    @autoreleasepool {
        [[NSThread currentThread] setName:@"AFNetworking"];
        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
        [runLoop run];
    }
    

    (2)自动释放池RunLoop内部有一个自动释放池,当RunLoop开启时,就会自动创建一个自动释放池,当RunLoop在休息之前会释放掉自动释放池的东西,然后重新创建一个新的空的自动释放池,当RunLoop被唤醒重新开始跑圈时,Timer,Source等新的事件就会放到新的自动释放池中,当RunLoop退出的时候也会被释放。
    注意:只有主线程RunLoop会默认启动。也就意味着会自动创建自动释放池,子线程需要在线程调度方法中手动添加自动释放池。

    参考文章:iOS底层原理总结深入理解RunLoop

    相关文章

      网友评论

          本文标题:Runloop相关阅读笔记

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