RunLoop源码学习

作者: wilsonhan | 来源:发表于2019-06-30 11:06 被阅读0次

    通常我们开发iOS app时接触到的是NSRunLoop,而NSRunLoop实际上是对苹果的Core Foundation框架中CFRunLoop的封装,这次我们直接通过官方文档和Core Foundation源码学习CFRunLoop

    Core Foundation是纯C版本的实现,苹果已经开源了Core Foundation的源码,相关链接: 官方文档 源码下载

    CFRunLoop

    头文件中的声明

    typedef struct __CFRunLoop * CFRunLoopRef;
    

    __CFRunLoop的定义(只保留了主要属性)

    struct __CFRunLoop {
        CFMutableSetRef _commonModes;//common mode集合,后边会讲
        CFMutableSetRef _commonModeItems;//source集合
        CFRunLoopModeRef _currentMode;//当前生效的mode
        CFMutableSetRef _modes;//当前runloop的所有mode
        struct _block_item *_blocks_head;//block链表头
        struct _block_item *_blocks_tail;//block链表尾部
    };
    

    可以看到,一个run loop对象包含的元素并不多,其中_commonModes_commonModeItems_currentMode_modes都跟mode有关,剩余两个属性是一个链表,用来保存block。

    那么mode是什么,接下来看一看mode的定义

    CFRunLoopMode

    如下是CFRunLoopModeRef的定义:

    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    struct __CFRunLoopMode {
        CFStringRef _name;//mode的名字
        Boolean _stopped;//用于退出当前mode的方法
        //输入源 source
        CFMutableSetRef _sources0;//非port相关事件(如用户点击)
        CFMutableSetRef _sources1;//port相关的事件(系统事件)
        //监听者 observer
        CFMutableArrayRef _observers;
        CFIndex _observerMask;//监听者注册的监听事件
        //计时器 timer
        CFMutableArrayRef _timers;
    };  
    

    一个mode包含三种类型的对象,sources(CFRunLoopSource)、timers(CFRunLoopTimer)和observers(CFRunLoopObserver),要接受run loop的回调,必须依赖这三种对象。他们和run loop以及mode之间的关系如下:

    image.png

    每一个线程对应一个run loop对象,每一个run loop包含多个mode,每一个mode包含多个source、timer以及observer。

    每个source、timer和observer必须添加到一个或多个mode中才能生效,但是run loop同时只能运行一个mode,如果当前运行的mode不是添加的mode,则不会生效。

    举个UIScrollView的例子

    主线程的run loop默认运行的是NSDefaultRunLoopMode,而当滑动UIScrollView时,主线程的run loop会切换到UITrackingRunLoopMode

    如果在主线程的NSDefaultRunLoopMode中加入了一个NSTimer,当用户滑动UIScrollView的时候,主线程会将run loop切换到UITrackingRunLoopMode,这时这个NSTimer就不会生效。直到用户停止滑动时,主线程将run loop切换回NSDefaultRunLoopMode,此时计时器才会生效。

    系统定义了一些mode,常见的有

    • 默认mode:NSDefaultRunLoopMode(Cocoa) / kCFRunLoopDefaultMode(Core Foundation)
    • 事件跟踪:UITrackingRunLoopMode(Cocoa)
    • Common Modes:NSRunLoopCommonModes(Cocoa) / kCFRunLoopCommonModes(Core Foundation)

    当我们需要执行一些优先级较高的任务时,也可以自定义mode,限制一些低优先级的事件,保证高优先级任务的执行。

    每一个Mode通过name区分,Core Foundation没有对外暴露run loop mode的接口,使用者只需要关心mode的name即可,如NSDefaultRunLoopModekCFRunLoopDefaultModeUITrackingRunLoopMode,他们都是string类型(可以通过toll-free bridge转换)。

    kCFRunLoopCommonModes

    __CFRunLoop的定义中,如下两句定义了common modes相关的变量:

    CFMutableSetRef _commonModes;//common mode集合
    CFMutableSetRef _commonModeItems;//source/timer/observer的集合
    

    Common modes不是一个mode,而是很多mode的集合。被添加到common modes中的mode,会存储在_commonModes中,同时会将_commonModeItems中的元素添加到这个mode中,这样_commonModeItems可以被_commonModes包含的每一个mode运行时监听到。

    从使用者的角度来说,当添加了一个timer到NSRunLoopCommonModes中,无论当前run loop运行在哪一种mode下,只要这个mode在common modes集合中,这个NSTimer就会生效。

    再拿上面UIScrollView举个例子

    在主线程中,NSDefaultRunLoopModeUITrackingRunLoopMode这两个mode被系统加入了common modes中。这意味着,我们如果将NSTimer加入到common modes中,也就是添加到kCFRunLoopCommonModes,此时无论用户是否滑动UIScrollView,这个NSTimer都会生效。

    这里有两种方式与common modes打交道,一个是添加自定义mode到common modes中,另一个是添加sources/timers/observers到kCFRunLoopCommonModes,来看看对应的实现

    1. CFRunLoopAddCommonMode
    void CFRunLoopAddCommonMode(CFRunLoopRef rl, CFStringRef modeName) {
        //判断common modes是否已经存在该mode
        if (!CFSetContainsValue(rl->_commonModes, modeName)) {
            //获取common modes set
            CFSetRef set = rl->_commonModeItems ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModeItems) : NULL;
            //添加mode进common modes set
            CFSetAddValue(rl->_commonModes, modeName);
            if (NULL != set) {
                CFTypeRef context[2] = {rl, modeName};
                //将common mode items中的每一个item添加到传入的mode中
                CFSetApplyFunction(set, (__CFRunLoopAddItemsToCommonMode), (void *)context);
            }
        }
    }
    

    当调用CFRunLoopAddCommonMode函数时,会将新的mode加入到该runloop的common modes集合中,同时,会将当前common mode items中的所有元素,添加到新的mode中。

    如此一来,无论run loop被切换到哪一个mode,只要这个mode被加入到kCFRunLoopCommonModes中,就可以响应那些被添加到kCFRunLoopCommonModes的sources/timers/observers。

    2. CFRunLoopAddSource/CFRunLoopAddObserver/CFRunLoopAddTimer

    可以在将source/timer/observer三种对象加入到run loop时传入kCFRunLoopCommonModes参数,这样run loop运行在common modes的mode时,这些source/timer/observer就会生效。这里仅放一个CFRunLoopAddTimer中与common modes有关的代码,其他两种(source/observer)代码都是类似的。

    void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) {   
        //判断是否是将timer加入到kCFRunLoopCommonModes
        if (modeName == kCFRunLoopCommonModes) {
            //获取common modes集合
            CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
            //如果common modes items为空,则创建一个common modes items的集合
            if (NULL == rl->_commonModeItems) {
                rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
            }
            //将传入的timer source加入到common mode items中
            CFSetAddValue(rl->_commonModeItems, rlt);
            //将timer添加到common modes中的每一个mode中
            if (NULL != set) {
                CFTypeRef context[2] = {rl, rlt};
                CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
            }
        } else {
          //非common mode的操作
        }
    }
    

    将一个timer加入到kCFRunLoopCommonModes中后,会将这个timer加入到common mode items里,同时也会将这个timer加入到common modes集合中的每一个mode中,也就是说,该函数会帮你更新所有在common modes集合中的mode,这样就达到了无论运行在任意一个common mode时,都可以使这个timer生效。

    CFRunLoopSource/CFRunLoopTimer/CFRunLoopObserver

    我们可以通过将source/timer/observer这三种对象加入到run loop中,当有事件发生时,通过回调接受通知。在加入到run loop时,必须指定一个mode。当然也可以从一个run loop中移除上述三种对象。

    CFRunLoopSourceRef

    Input source是事件发生的来源,通常产生异步事件,比如消息到达网络端口或者用户执行的操作。它的定义如下:

    typedef struct CF_BRIDGED_MUTABLE_TYPE(id) __CFRunLoopSource * CFRunLoopSourceRef;//toll-free bridge
    struct __CFRunLoopSource {
        CFMutableBagRef _runLoops;
        union {
                CFRunLoopSourceContext version0;//source0
            CFRunLoopSourceContext1 version1;//source1
        } _context;
    };
    

    CFRunLoopSourceRef中,最主要的是两个变量CFRunLoopSourceContextCFRunLoopSourceContext1,它们用union来声明,意味着有两种不同的source,source0和source1。

    source0

    source0是一般响应应用程序事件,例如按钮的响应事件。当需要发送source0事件时,调用CFRunLoopSourceSignal函数将这个source标记为待触发,但是该函数不能唤醒runloop,需要再调用CFRunLoopWakeUp方法唤醒对应的run loop,这个source0事件才会被触发。Core Foundation中的CFSocket使用的是source0的方式实现。定义如下:

    typedef struct {
        CFIndex version;//0代表是source0
        void    (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
        void    (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
        void    (*perform)(void *info);
    } CFRunLoopSourceContext;
    

    它包含三个回调,

    • schedule:当添加source0到run loop时,会调用一次schedule回调方法;
    • perform:当触发source0时,会调用perform回调方法;
    • cancel:当移除或者run loop销毁时,会调用cancel回调方法。
    source1

    source1由run loop和内核管理,使用mach port进行通信,用于通过内核进行进程间通信,也可以用于线程间的通信。source1能够将run loop唤醒。Core Foundation中的CFMachPortCFMessagePort通过source1的方式实现。定义如下:

    typedef struct {
        CFIndex version;//1代表source1
        mach_port_t (*getPort)(void *info);
        void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
    #endif
    } CFRunLoopSourceContext1;
    

    其中

    • getPort函数指针提供一个获取port的函数
    • perform函数是回调函数

    有趣的是,CFMessagePort通常用于进程间通信,如iOS越狱开发,常用的场景是前端有一个UI程序用于界面展示,后端有一个daemo精灵程序用于任务处理。而官方文档中有提到,CFMessagePort已不能再iOS7之后的系统中使用。来源

    CFRunLoopSourceRef的使用

    Core Foundation提供了提供了与input sourc交互的函数

    • CFRunLoopSourceGetContext:创建source0或者source1变量
    • CFRunLoopSourceCreate:创建CFRunLoopSourceRef
    • CFRunLoopAddSource:添加CFRunLoopSourceRef到run loop中

    CFRunLoopTimerRef

    CFRunLoopTimerRef是和NSTimer toll-free bridge的计时器。Run loop timer并不一定可靠,如果加入的mode没有运行或者当前run loop在执行一段耗时的操作,run loop timer可能不会被触发。而且Run loop timer的触发时间依赖于计划的时间间隔,而不是实际运行的时间间隔,比如一个5秒重复的计时器,在第二次时延时了2秒,那么第三次的执行时间不会改变,仍然是第15秒时执行。

    Run loop timer可以同时加入到多个run loop mode中,但中只会在第一个加入的run loop内生效。

    CFRunLoopTimerRef主要包含了interval参数和Callback回调方法。

    Run loop timer关于时间计算的机制以后再深入了解

    CFRunLoopObserverRef

    CFRunLoopObserverRef是一个观察者,它包含了一个回调指针和一个Activity参数,用于表明接受的run loop事件,具体事件定义如下,当run loop处于不同状态时,会通知obsever。

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry         = (1UL << 0), // 即将进入Loop
        kCFRunLoopBeforeTimers  = (1UL << 1), // 即将处理 Timer
        kCFRunLoopBeforeSources = (1UL << 2), // 即将处理 Source
        kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
        kCFRunLoopAfterWaiting  = (1UL << 6), // 刚从休眠中唤醒
        kCFRunLoopExit          = (1UL << 7), // 即将退出Loop
    };
    

    blocks

    __CFRunLoop的定义中,还有两个关于block链表的定义

    struct _block_item *_blocks_head;
    struct _block_item *_blocks_tail;
    

    Run loop还提供了一个CFRunLoopPerformBlock函数用于添加block,和source0类似,这个方法不会主动唤醒run loop,需要调用CFRunLoopWakeUp函数主动唤醒run loop。

    CFRunLoopPerformBlock的定义如下

    void CFRunLoopPerformBlock(CFRunLoopRef rl, CFTypeRef mode, void (^block)(void))
    

    在run loop运行的每一次循环中,会多次检查当前是否有需要执行的block,如果有则会执行传入的block

    CFRunLoopGet

    在通常开发时,我们通常不需要关心run loop的生命周期。系统会自动在主线程帮我们创建run loop对象。可以通过如下接口获得主线程的run loop对象,该方法返回一个CFRunLoopRef实例。

    CFRunLoopRef CFRunLoopGetMain(void);
    

    当我们创建一个新的线程时,默认是没有初始化run loop对象的,需要调用函数获取当前线程的run loop对象。

    CFRunLoopRef CFRunLoopGetCurrent(void);
    

    如此看来,创建run loop对象的函数就是Core Foundation内部实现的了,CFRunLoopGetMainCFRunLoopGetCurrent这两个函数内部都会调用同一个方法_CFRunLoopGet0(pthread_t t),来看下具体的代码。

    这里简单提一下TSD(thread specific data)的概念,在多线程环境中,因为数据空间是共享的,所以全局变量也为所有线程所共有。所以当需要一个仅在当前线程中可以访问的数据时,使用TSD来存储。TSD存储的数据仅在当前线程有效,但是可以跨函数访问。

    run loop对象就存储在TSD中。

    CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        //__CFRunLoops是全局保存runloop对象的dict,首次运行时初始化该dict
        if (!__CFRunLoops) {
            __CFUnlock(&loopsLock);
            //创建全局ditc,并添加主线程的runloop 对象
            CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
            //通过__CFRunLoopCreate方法创建主线程的run loop对象
            CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np());
            CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
            //将dict与__CFRunLoops指针互换,然后释放ditc
            if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) {
                CFRelease(dict);
            }
        }
        //通过线程对象获取对应的runloop
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        //如果该线程还没有创建runloop对象,那么初始化该线程的runloop对象
        if (!loop) {
            CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            if (!loop) {
                CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
                loop = newLoop;
            }
        }
        //判断是否获取当前线程的runloop对象
        if (pthread_equal(t, pthread_self())) {
            //将run loop对象存放到TSD中
            _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
            if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
                //在这里设置销毁的回调方法,当线程生命周期结束时销毁runloop对象
                _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
            }
        }
        return loop;
    }
    

    该函数的流程如下

    1. 第一次进入时,会创建一个全局的dict,用于存储每一个线程对应的run loop对象,同时也会初始化主线程的run loop对象。
    2. 根据传入的线程获取对应的run loop对象,若为空,则创建一个run loop对象,并添加到全局dict中。同时也会将这个run loop放到对应线程的TSD中并设置一个线程结束时的销毁函数回调。

    通过上面的函数,我们可以获取到当前线程的run loop对象。在执行前面所讲的添加source/timer/observers函数时,都需要传入run loop对象。

    系统会启动主线程的run loop的运行,对于其他线程,需要我们在获取run loop对象后主动启动。

    CFRunLoopRun

    首先通过一张图,了解下CFRunLoopRun的主要逻辑。

    image.png

    PS:图中左边的source0(port)应该是source1(port)

    在Core Foundation提供了两个函数供我们启动run loop,CFRunLoopRunCFRunLoopRunInMode,函数声明如下:

    //无参数,直接启动run loop,运行在default mode
    void CFRunLoopRun(void);
    //设置run loop运行的mode,有效期以及是否在运行source 0事件后直接退出,返回值为run loop退出的原因字段
    SInt32 CFRunLoopRunInMode(CFStringRef mode, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
    

    这两个函数都会调用CFRunLoopRunSpecific函数,该函数实现如下,注释中的数字对应图中的顺序

    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) {
        //判断mode中是否有source,timer或者block事件,如果没有,run loop会立即退出
        if (__CFRunLoopModeIsEmpty(rl, currentMode, rl->_currentMode)) {
            return kCFRunLoopRunFinished;
        }
        int32_t retVal = kCFRunLoopRunFinished;
        //1. run loop即将进入,通知Observers
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
          do {
            //2. 通知观察者,即将触发计时器
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            //3. 通知观察者即将触发source0(非port based)输入源
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
            //处理blocks
            __CFRunLoopDoBlocks(rl, rlm);
            //4. 处理source0,也就是非port based输入源
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            if (sourceHandledThisLoop) {
                //处理完source0后会再处理下blocks
                __CFRunLoopDoBlocks(rl, rlm);
            }
            //5. 检查是否有需要处理的source1事件,通常是系统级事件,倒数第三个参数传0表示立即返回
            if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                //跳转到handle_msg直接去处理source1事件
                goto handle_msg;
            }
            
            //没有source1则直接进入睡眠
            //6. 通知观察者Runloop即将进入睡眠
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            //7. 调用系统内核方法mach_msg,切换到内核态接受消息
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
            //当有source1,timer或者手动唤醒时,会退出睡眠态
            //8. 通知观察者Runloop退出等待
            __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
    
            handle_msg:;
            //9. 退出睡眠后,需要处理些事件
            //计时器需要触发
            if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                //触发计时器
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    //计算下一次触发的时间
                    __CFArmNextTimerInMode(rlm, rl);
                }
            } else if (livePort == dispatchPort) {
                //如果有dispatch到main_queue的block,执行block。
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
            } else {//source1事件
                __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply);
            }
            //再次执行一次block回调
            __CFRunLoopDoBlocks(rl, rlm);
            
            //判断是否需要退出run loop
            if (sourceHandledThisLoop && stopAfterHandle) {
                //启动run loop时设置了执行source0后立即退出
                retVal = kCFRunLoopRunHandledSource;
            } else if (timeout_context->termTSR < mach_absolute_time()) {
                //启动run loop时设置了超时时间
                retVal = kCFRunLoopRunTimedOut;
            } else if (__CFRunLoopIsStopped(rl)) {
                //run loop被设置为停止
                retVal = kCFRunLoopRunStopped;
            } else if (rlm->_stopped) {
                //run loop mode被设置为停止
                rlm->_stopped = false;
                retVal = kCFRunLoopRunStopped;
            } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
                //当前mode被移除了
                retVal = kCFRunLoopRunFinished;
            }
        } while (0 == retVal);
        //10. run loop已经退出,通知observers
          __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
      
        return retVal;
    }
    

    通过代码可以了解到

    • run loop通过do…while循环实现,只要不满足退出的条件,run loop就会睡眠或者运行。
    • run loop需要添加source,timer或者block事件才能运行,否则会直接退出
    • run loop进入休眠时调用mach_msg函数切换到内核态(当我们在app运行时点击暂停,就可以看到调用栈停留在mach_msg_trap()这个方法)
    image.png

    PerformSelecter

    当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。

    当调用 performSelector:onThread: 时,实际上其会创建一个source0 加到对应的线程去,同样的,如果对应线程没有 RunLoop 该方法也会失效。

    苹果对RunLoop的应用

    了解了RunLoop的实现,我们看一看苹果如何使用RunLoop的。该节主要参考深入理解RunLoop,这里仅做部分展开。

    viewDidLoad方法中增加断点,打印[NSRunLoop currentRunLoop]可以打印出当前线程的runLoop对象的一些详细信息

    <CFRunLoop 
    current mode = kCFRunLoopDefaultMode,
    common modes = {
         "UITrackingRunLoopMode"
         "kCFRunLoopDefaultMode"
    }
    
    common mode items = {
      //source 0
        CFRunLoopSource { 
        order = -1, version = 0, callout = PurpleEventSignalCallback 
      }
        CFRunLoopSource { 
        order = -1, version = 0, callout = __handleEventQueue 
      }
        CFRunLoopSource { 
        order = 0, version = 0, callout = FBSSerialQueueRunLoopSourceHandler 
      }
        CFRunLoopSource {
        order = -2, version = 0, callout = __handleHIDEventFetcherDrain
      }
    
      //source 1
        CFRunLoopSource {
        order = 0, port = 43275
      }
        CFRunLoopSource {
        order = -1, version = 1, callout = PurpleEventCallback
      }
        CFRunLoopSource {
        order = 0, port = 42755
      }
    
      //UI绘制相关
        CFRunLoopObserver {
        activities = 0xa0, order = 1999000, callout = _beforeCACommitHandler
      }
        CFRunLoopObserver {
        activities = 0xa0, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv
      }
        CFRunLoopObserver {
        activities = 0xa0, order = 2001000, callout = _afterCACommitHandler
      }
    
      //自动释放池(auto release pool)相关
        CFRunLoopObserver {
        activities = 0x1, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
      }
        CFRunLoopObserver {
        activities = 0xa0, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler
      }
    
        CFRunLoopObserver {
        activities = 0x20, order = 0, callout =_UIGestureRecognizerUpdateObserver
      }
    }
    
    modes = {
        //RunLoop包含的mode以及mode中的observers/sources/timers
    }
    

    AutoreleasePool

    苹果在主线程RunLoop的CommonModes中注册了两个Observer,其中回调函数都是_wrapRunLoopWithAutoreleasePoolHandler

    <CFRunLoopObserver>{
      order = -2147483647, activities = 0x1, callout = _wrapRunLoopWithAutoreleasePoolHandler
    }
    <CFRunLoopObserver>{
      order = 2147483647, activities = 0xa0, callout = _wrapRunLoopWithAutoreleasePoolHandler
    }
    

    第一个Observer设置了最高优先级-2147483647,在进入RunLoop时会触发(0x1)。

    第二个Observer设置了最低优先级2147483647,在RunLoop进入睡眠或者退出时触发(0xa0)。

    _wrapRunLoopWithAutoreleasePoolHandler

    通过在XCode中添加Symbolic Breakpoint增加断点,查看_wrapRunLoopWithAutoreleasePoolHandler对应汇编的代码。

    image.png

    启动App后,会进入该断点,可以看到代码中有两处地方会跳转到其他函数,分别是NSPushAutoreleasePoolNSPopAutoreleasePool,根据名字可以看出,一个是autlreleasePool的创建,另一个是autlreleasePool的释放,推测在该函数中会判断RunLoop当前的状态,然后执行不同的函数。

    然后我们继续对NSPushAutoreleasePoolNSPopAutoreleasePool添加断点,最终调用函数分别是objc_autoreleasePoolPushobjc_autoreleasePoolPop

    第一个Observer在进入RunLoop时,创建autoreleasePool,其order=-2147483647保证了是所有回调之前调用。

    第二个Observer在RunLoop休眠时,首先释放autoreleasePool,然后创建autoreleasePool;在RunLoop退出时,释放autoreleasePool,其order=2147483647保证了是所有回调之后调用。

    总结

    本文主要对Core Foundation框架中的RunLoop源码进行学习,了解了RunLoop的实现原理,后续会对RunLoop的应用进一步探索研究。

    相关文章

      网友评论

        本文标题:RunLoop源码学习

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