II: Internal structure of RunLoop

RunLoopMode

There are several core classes of iOSRunLoop, which are:

  • CFRunLoop
  • CFRunLoopMode
  • CFRunLoopSource
  • CFRunLoopObserver
  • CFRunLoopTimer

RunLoopMode definition

The following are some definitions of RunLoop and RunLoopMode in the source code:

struct CFRunLoop {
...
    pthread_t _pthread;
    CFMutableSetRef _commonModes;
    CFMutableSetRef _commonModeItems;
    CFRunLoopModeRef _currentMode;
    CFMutableSetRef _modes;
...
}

struct CFRunLoopMode {
...
    CFMutableSetRef _source0;
    CFMutableSetRef _source1;
    CFMutableArrayRef _observers;
    CFMutableArrayRef _timers;
...
}
复制代码

According to the definitions of RunLoop and RunLoopMode above, we can draw the following correspondence: a RunLoop can contain multiple Modes, and a mode can contain multiple Source0 & Source1 & Observer & Timer.

RunLoop的过程.001.jpeg

Each time the main function of RunLoop is called, only one of the Modes can be specified, and this Mode is called Common Mode. If you need to switch Mode, you can only exit RunLoop and re-specify Mode to enter. This is done to separate different groups of Source/Timer/Observer so that they do not affect each other. RunLoop only runs in one Mode at a time.

CFRunLoopSourceRef

Source is an abstract class of RunLoop's data source (input source). Source has two versions: Source 0 and Source 1.

struct __CFRunLoopSource {
    CFRuntimeBase _base;
    uint32_t _bits;
    pthread_mutex_t _lock;
    CFIndex _order;			/* immutable */
    CFMutableBagRef _runLoops;
    
    // 共用体
    // 同一时刻,共用体只存放了一个被选择的成员
    union {
        CFRunLoopSourceContext version0;	/* immutable, except invalidation */
        CFRunLoopSourceContext1 version1;	/* immutable, except invalidation */
    } _context;
};
复制代码

Two different types of Source are distinguished by the difference of version, and these two sources also have obvious differences.

Source0

Souece0 contains a callback (function pointer), which cannot directly wake up RunLoop, but can only actively call methods to wake up RunLoop. If we create a Source0 ourselves and want to wake up RunLoop through Source0, we need two steps:

  1. Send a signal:CFRunLoopSourceSignal(rs)
  2. Manual wake up:CFRunLoopWakeUp(RunLoop.main.getCFRunLoop())

也就是说我们需要去调用CFRunLoopSourceSignal(source), 来标记这个Source正在等待处理,然后需要手动调用**CFRunLoopWakeUp(runloop)**来唤醒RunLoop,让它来处理这个事件。

Source1

它被RunLoop和kernel管理,通过mach_port来驱动(特指基于port的事件),比如CFMachPort, CFMessagePort,NSSocketPort。Mach Port是很轻量级的方式用于进程之间的通信,它可以被理解为一个通信的channel。比如点触事件,其实就是由BackService直接将IOEvent传递给处理该事件的前台进程。这就涉及到了进程间的通信,使用的就是Source1。

CFRunLoopTimerRef

基于时间的触发器,它和NSTimer是toll-free bridged的,可以混用(它的底层是基于mk_timer的)。它是会被RunLoop Mode所影响的(而GCD的timer是不会被RunLoopMode影响的)。当它被加入RunLoop之后,RunLoop将会注册与之对应的时间点,当时间点到达的时候,Runloop将被唤醒来执行回调,如果这个线程被阻塞了或者此时RunLoop不在这个Mode中,那么这个触发的时间点点并不会执行回调,将等到下一次循环的时间点被触发。

CFRunLoopObserverRef

它是观察者,每个Observer都包含了对应一个回调(函数指针),一个Mode中可以有多个观察者,当RunLoop的状态发生改变时,观察者就可以通过回调接受这个变化。可以观测的时间点有以下几个:

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
};
复制代码

这些source & timer & observer都统一被称为mode item,一个item可以被同时加入多个mode。但是一个item被重复加入同一个mode时是不会有效果的。如果一个mode中一个item都没有,那么RunLoop就会直接退出,不进入循环。

Common Modes

同时要注意有一个概念叫做Common Mode。一个Mode可以把自己标记为Common(将mode添加到CommonMode中)。

// 当然Mode中需要加observer/Timer/source
let mode = CFRunLoopMode.init(rawValue: "fff" as CFString)
CFRunLoopAddCommonMode(RunLoop.main.getCFRunLoop(), mode)
复制代码

这样的话,每当RunLoop的内容变化时(切换Mode运行时),RunLoop会自动将commonModeItems中的Source & Observer & Timer同步到具有"Common"标记的所有Mode中。

举个例子:主线程的RunLoop中有两个预先设置的Mode,其一是kCFRunLoopDefaultMode, 其二是UITrackingRunLoopMode。这两个Mode都被标记为Common属性。DefaultMode是默认状态,TrackingMode是追踪ScrollView滑动时的状态。当创建一个Timer时,Timer会得到重复的回调。但是当滑动一个UIScrollView时,RunLoop会将Mode切换都TrackingMode,这个时候Timer的回调就不会生效了。

所以需要这个Timer在两个Mode中都可以得到回调,那么其中一种方式就是将这个Timer分别放入到这两个Mode中。还有一种方式,就是将Timer放到顶层的RunLoop的CommonModeItems中,这其中的items都会被自动更新到所有具备Common属性的Mode中。

Mode的分类

从Apple的官方文档中,我们可知其实系统提供了几个Mode:

  • kCFRunLoopDefaultMode: App的默认Mode,通常主线程是在这个Mode下运行的。
  • UITrackingRunLoopMode:界面追踪Mode,用于ScrollView的追踪,保证界面滑动不受影响
  • UIInitializationRunLoopMode:刚启动App时进入的第一个Mode,启动后不再使用
  • GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
  • KCFRunLoopCommonModes:占位的Mode

Mode实例

直接将当前线程的RunLoop在控制台输出,可以得到如下的结构:

CFRunLoop {
    current mode = KCFRunLoopDefaultMode,
    common modes = {
        UITrackingRunLoopMode,
        KCFRunLoopDefaultMode
        }
    
    common mode items = {
        //source 0
        CFRunLoopSource = { order = -1,{
            callout = PurpleEventSignalCallback}}   //GraphicsServices
        CFRunLoopSource = { order = 0,{
            callout = HandleDelegateSource(void*)}} //WebCore
        CFRunLoopSource = { order = 0, {
            callout = __NSThreadPerformPerform}}    //Foundation
        CFRunLoopSource = { order = 0, {
            callout = FBSSerialQueueRunLoopSourceHandler}} // FrontBoardServices
        CFRunLoopSource = { order = -1,{
            callout = __eventQueueSourceCallback}}   //UIKitCore
        CFRunLoopSource = { order = 0,{
            callout = WTF::RunLoop::performWork(void*)}}  //JavaScriptCore
        CFRunLoopSource = { order = -2,{
            callout = __eventFetcherSourceCallback}} //UIKitCore
        
        // source 1(mach port)
        CFRunLoopSource {order = 0,  {port = 17923}}
        CFRunLoopSource {order = 0,  {port = 12039}}
        CFRunLoopSource {order = 0,  {port = 16647}}
        CFRunLoopSource {order =-1, {
            callout = PurpleEventCallback}}
        CFRunLoopSource {order = 0, {port = 2407,
            callout = _ZL20notify_port_callbackP12__CFMachPortPvlS1_}}
        CFRunLoopSource {order = 0, {port = 1c03,
            callout = __IOHIDEventSystemClientAvailabilityCallback}}
        CFRunLoopSource {order = 0, {port = 1b03,
            callout = __IOHIDEventSystemClientQueueCallback}}
        CFRunLoopSource {order = 1, {port = 1903,
            callout = __IOMIGMachPortPortCallback}}
        
        
        // observer
        CFRunLoopObserver {order = -2147483647, activities = 0x1, // Entry
            callout = _wrapRunLoopWithAutoreleasePoolHandler}
        CFRunLoopObserver {order = 0, activities = 0x20,          // BeforeWaiting
            callout = _UIGestureRecognizerUpdateObserver}
        CFRunLoopObserver {order = 1999000, activities = 0xa0,    // BeforeWaiting | Exit
            callout = _beforeCACommitHandler}// UIKitCore 
        CFRunLoopObserver {order = 1999000, activities = 0xa0,    // BeforeWaiting | Exit
            callout = _afterCACommitHandler} // UIKitCore
        CFRunLoopObserver {order = 2000000, activities = 0xa0,    // BeforeWaiting | Exit
            callout = CA::Transaction::observer_callback(__CFRunLoopObserver*, unsigned long, void*)}
        CFRunLoopObserver {order = -2147483648, activities = 0x46,// BeforeTimer | BeforeSource | AfterWait
            callout = __trackRunLoopTimes}   // UIKitCore  
        CFRunLoopObserver {order = 2147483647, activities = 0x20, // BeforeWaiting
            callout = __trackRunLoopTimes}   // UIKitCore  
        CFRunLoopObserver {order = 2000000, activities = 0xa0,    // BeforeWaiting | Exit
            callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv}
        CFRunLoopObserver {order = 2147483647, activities = 0xa0, // BeforeWaiting | Exit
            callout = _wrapRunLoopWithAutoreleasePoolHandler}
        
        // timer
        CFRunLoopTimer {valid = Yes, firing = No, interval = 3.1536e+09, tolerance = 0, 
            next fire date = 1.17402502e+09 (504911209 @ 12130246606742925),
            callout = CA::timer_callback(__CFRunLoopTimer*, void*)} // QuartzCore
        CFRunLoopTimer {valid = Yes, firing = No, interval = 3, tolerance = 0, 
             next fire date = 669113787 (-20.825485 @ 12377085760065),
             callout = [FSPagerView.FSPagerView flipNextWithSender:]}
        }
    modes = {
         CFRunLoopMode  { //kCFRunLoopDefaultMode
            sources0 =  { /* same as 'common mode items' */ },
            sources1 =  { /* same as 'common mode items' */ },
            observers = { /* same as 'common mode items' */ },
            timers =    { /* same as 'common mode items' */ },
        },
 
        CFRunLoopMode  { //UITrackingRunLoopMode
            sources0 =  { /* same as 'common mode items' */ },
            sources1 =  { /* same as 'common mode items' */ },
            observers = { /* same as 'common mode items' */ },
            timers =    { /* same as 'common mode items' */ },
        },
        CFRunLoopMode {  // kCFRunLoopCommonModes
            source0 = null,
            source1 = null,
            timers = null,
            observers = null,
        },
        CFRunLoopMode { // GSEventReceiveRunLoopMode
            source0 = {
                CFRunLoopSource {order = 1,
                    callout = PurpleEventSignalCallback}
            },
            source1 = {},
            observers = null,
            timers = null,
        }
    }
    }
}
复制代码

我们可以很明确的看到,一个RunLoop下对应有很多种不同的Mode,而每个Mode中都有对应的source,observer等元素。当然要想看到更多的Mode类型的话,可以到Apple关于RunLoop的官方描述中查看。

RunLoop的结构

接下来我直接PO一段RunLoop的源码:

// GitHub上开源的CoreFoundation,run方法其实还是调用了运行defaultMode的方法
extension RunLoop {
    public func run() {
        while run(mode: .default, before: Date.distantFuture) { }
    }
   ...
}

// 使用指定的模式来启动RunLoop
int CFRunInMode(modeName, seconds, returnAftersourceHandled) {
    return CFRunloopRunSpecific(CFRunLoopGetCurrent(),
                                modeName,
                                seconds, 			       
                                returnAftersourceHandled);
}

int CFRunloopRunSpecific(runloop, modeName, seconds, returnAftersourceHandled) {
    // 根据mode名找到对应的mode
    CFRunLoopModeRef currentMode = __CFRunLoopMode(r1, modeName, false);
    
    if (Null == currentMode || CFRunLoopModeIsEmpty(currentMode)) {
        return;
    }
    
    int result = KCFRunLoopRunFinished;
    
    // 1、通知observers:RunLoop即将进入RunLoop (KCFRunLoopEntry)
    CFRunLoopDoObservers(rl, currentMode, KCFRunLoopEntry);
    
	result = __CFRunLoopRun(runloop, currentMode, ...);
    
    // 内部函数
    int __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled) {
        bool didDispatchPortLastTime = true;
        in retVal = 0;
        
        do {
            // runloopMode中的所有port
            __CFPortSet waitSet = rlm->_portSet;
            
            // 2、通知observers:RunLoop即将准备处理timer
            CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
            
            // 3、通知observers:RunLoop即将准备处理source(不基于port的即source0)
            CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
            
            // -- 执行被加入的block
            CFRunLoopDoBlocks(rl, rlm);
            
            // 4、处理Source0:Runloop处理source(不基于port的source)
            bool sourceHandledThisLoop = CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            
            // -- 执行被加入的block
            if (sourceHandledThisLoop) {
                CFRunLoopDoBlocks(rl, rlm);
            }
            
            // 5、如果有Source1处于Ready状态:RunLoop跳到第九步处理Source(基于port的source)
            // 这里指定了接收消息的端口是dispatchPort:如Dispatch.main.async{}
            if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
            	bool hasMsg = CFRunLoopServiceMachPort(dispatchPort, msg, &livePort, 0);
            	if (hasMsg) goto handle_msg;
            }
            
          	didDispatchPortLastTime = false;
            
            // 6、通知Observers:RunLoop即将进入休眠(kCFRunLoopBeforeWaiting)
            CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting);
            
            // 7、线程进入睡眠:调用CFRunLoopServiceMachPort等待被唤醒,被以下几个唤醒
            //    * 一个基于port的source事件(·)
            //    * 一个到点要执行的timer(Timer)
            //    * RunLoop要超时了(RunLoopTimeOut)
            //    * RunLoop被显式的唤醒了
            CFRunLoopServiceMachPort(waitSet, msg, livePort, TIMEOUT_INFINITY);
            
            // 8、通知Observers:线程被唤醒了
            CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
            
            // 9、收到消息,处理消息。
            handle_msg;
            
            // * 无port啥也不做
            if (Mach_Port_Null == livePort) {
                CFRUNLOOP_WAKEUP_FOR_NOTHING();
                
            // * 标记为唤醒
            } else if (livePort == rl->_wakeUpPort) {
                CFRUNLOOP_WAKEUP_FOR_WAKEUP();
                

            // * 标记为Timer唤醒:Timer/NSTimer唤醒
            } else if (livePort == rlm->_timerPort) {
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                
                if (!_CFRunLoopDoTimers(rl, rlm, mach_absolute_timer())) {
                    CFArmNextTimerInMode(rlm, rl);
                }
            // * 标记为GCD唤醒:由main_queue派发的block
            } else if (livPort === dispatchPort) {
            	CFRUNLOOP_WAKEUP_FOR_DISPATCH();
                
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
                
                sourceHandledThisLoop = true;
            	didDispatchPortLastTime = true;
            // * 标记为Source1唤醒
            } else {
                CFRUNLOOP_WAKEUP_FOR_SOURCE();
                
                CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
                
                __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply)
         	}
            
            // 执行加入到runloop中的block
            CFRunLoopDoBlocks(rl, rlm);
            
            // 判断runloop是否结束
            // * 刚刚处理完事件
            if (sourceHandledThisLoop && stopAfterHandle) {
				retVal = kCFRunLoopRunHandledSource;
                
            // * runloop超时了
            } else if (timeout) {
                retVal = kCFRunLoopRunTimedOut;
                
            // * 被外部调用者强制停止了
            } else if (CFRunLoopIsStopped(rl)) {
            	retVal = kCFRunLoopRunStopped;
            } else if (rlm->_stopped) {
            	rlm->_stopped = false;
                
                retVal = kCFRunLoopRunStopped;
               
            // * runloop的Mode中source/timer/observer一个都没有了
            } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) {
            	retVal = kCFRunLoopRunFinished;
            }
            
        } while (0 == retValue);
    }
    
    //10、通知Observers:RunLoop即将离开
    CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
}
复制代码

它的内部逻辑是一个while循环,可以一直使线程处于存活的状态,有事情来就处理事情,没有事情来,就处于休息状态。基于上方的流程,我也创建了一个RunLoop流程图。

11.png

根据上述的流程,我们可以梳理出相应的回调:

{
    /// 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);
}
复制代码

Guess you like

Origin juejin.im/post/7119402150580453384