美文网首页
iOS RunLoop底层探索

iOS RunLoop底层探索

作者: RephontilZhou | 来源:发表于2018-11-03 00:02 被阅读137次

    【原创博文,转载请注明出处!】

    CFRunLoopRef源码在此!

    RunLoop:运行循环,在程序运行过程中循环做一些事情。所涉及的范畴包括:
    ① 定时器;
    ② GCD Async Main Queue;
    ③ 事件响应、手势识别、界面刷新;
    ④ 网络请求;
    ⑤ AutoreleasePool。

    RunLoop_Circle.png

    通常情况下如果我们创建一个Command Line Tool项目,如下:

    Mac上面 Command Line Tool项目的main函数.png

    程序在执行到Line13之后,马上会执行Line15,然后程序退出。
    然而在我们的iOS项目中,main函数可不是这么写的。

    iOS项目中main函数.png runloop大概做的事情类似于do...while循环.png

    有了RunLoop,程序并不会马上退出,而是保持运行状态。RunLoop可以处理App中的各种事件(比如触摸事件、定时器事件等);RunLoop可以在该做事的时候做事,该休息的时候休息,从而节约CPU资源,提高程序性能。

    iOS中提供两套API来访问和使用RunLoop,分别为Foundation框架下面的NSRunLoop和Core Foundation下面的CFRunLoopRef。
    NSRunLoop和CFRunLoopRef都是RunLoop对象,其中NSRunLoop是基于CFRunLoopRef的一层OC包装(参考图NSRunLoop是基于CFRunLoopRef的一层OC包装.png),CFRunLoopRef是开源的,开源地址已经在上文给出。

    NSRunLoop是基于CFRunLoopRef的一层OC包装.png

    上图中,touch事件在主线程中触发,主线程所对应的RunLoop为mainRunLoop,通过OC与C语言的API访问RunLoop地址,可见两者指针不同,但是NSLog(@"%@",[NSRunLoop currentRunLoop]);输出的RunLoop详细信息发现NSRunLoop本质也是一个CFRunLoop,并且其地址与CFRunLoopGetMain()相同,进而可以说明NSRunLoop是基于CFRunLoopRef的一层OC包装。

    RunLoop与线程的关系

    平时都是通过CFRunLoopGetCurrent(),CFRunLoopGetMain();API获取当前线程和主线程,但是这两个函数具体做了什么?让我们通过CFRunLoop源码来一探究竟:

    CFRunLoopRef CFRunLoopGetMain(void) {
        CHECK_FOR_FORK();
        static CFRunLoopRef __main = NULL; // no retain needed
        if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
        return __main;
    }
    
    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK();
        CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
        if (rl) return rl;
        return _CFRunLoopGet0(pthread_self());
    }
    
    

    在调用CFRunLoopGetCurrent(),CFRunLoopGetMain()函数时,函数内部都会调用_CFRunLoopGet0(pthread_t t)函数,并传入不同的线程作为参数。可见调用CFRunLoopGetMain()时,传入的是主线程,调用CFRunLoopGetCurrent()时传入的是当前的线程(子线程或主线程)。接下来继续看看_CFRunLoopGet0(pthread_t t)函数:

    // should only be called by Foundation
    // t==0 is a synonym for "main thread" that always works
    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);
        }
        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;
    }
    

    透过_CFRunLoopGet0(pthread_t t)源码,如果__CFRunLoops不存在,系统就会利用当前的主线程"pthread_main_thread_np()"创建一个mainLoop,也就是“mainRunLoop”,然后将创建RunLoop的线程作为key,RunLoop作为value添加到一个全局的字典CFDictionarySetValue中。最后获取"pthread_t"线程对应的loop并返回loop。如果"pthread_t"不是主线程,用同样的方法创建一个新的RunLoop,即"newLoop",同样将"newLoop"存放到字典中然后返回newLoop。所以关于RunLoop与线程的关系可以总结如下:

    • 每条线程都有唯一的一个与之对应的RunLoop对象;
    • RunLoop保存在一个全局的Dictionary里 ,线程作为key , Runloop作为value;
    • 线程刚创建时并没有RunLoop对象, RunLoop会在第一次获取它时创建;
    • RunLogp会在线程结束时销毁;
    • 主线程的RunLoop已经自动获取(创建) ,子线程默认没有开启Runloop。
    RunLoop对象里面包含哪些信息?

    - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event方法中简单通过NSLog(@"%@",[NSRunLoop currentRunLoop]);获取信息如下:

    2018-11-03 00:38:45.865615+0800 RunLoop_SearchTest[64702:13010624] <CFRunLoop 0x6000001f8200 [0x109e9cc80]>{wakeup port = 0x2503, stopped = false, ignoreWakeUps = false, 
    current mode = kCFRunLoopDefaultMode,
    common modes = <CFBasicHash 0x600000245c40 [0x109e9cc80]>{type = mutable set, count = 2,
    entries =>
        0 : <CFString 0x10b20ce88 [0x109e9cc80]>{contents = "UITrackingRunLoopMode"}
        2 : <CFString 0x109e72818 [0x109e9cc80]>{contents = "kCFRunLoopDefaultMode"}
    }
    ,
    common mode items = <CFBasicHash 0x600000245f10 [0x109e9cc80]>{type = mutable set, count = 16,
    entries =>
        0 : <CFRunLoopSource 0x600000167800 [0x109e9cc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10ed3e75a)}}
        1 : <CFRunLoopSource 0x604000168040 [0x109e9cc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2d03, callout = PurpleEventCallback (0x10ed40bf7)}}
        3 : <CFRunLoopObserver 0x604000139aa0 [0x109e9cc80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x10a63057f), context = <CFRunLoopObserver context 0x6040000cc940>}
        4 : <CFRunLoopObserver 0x60000013a360 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10fb70672), context = <CFRunLoopObserver context 0x0>}
        6 : <CFRunLoopObserver 0x60400013a720 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x10a079e7c), context = <CFRunLoopObserver context 0x7f8c1ac00d60>}
        8 : <CFRunLoopObserver 0x60400013a5e0 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10a04adf2), context = <CFArray 0x604000456950 [0x109e9cc80]>{type = mutable-small, count = 1, values = (
        0 : <0x7f8c1b008048>
    )}}
        9 : <CFRunLoopObserver 0x60400013a540 [0x109e9cc80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10a04adf2), context = <CFArray 0x604000456950 [0x109e9cc80]>{type = mutable-small, count = 1, values = (
        0 : <0x7f8c1b008048>
    )}}
        12 : <CFRunLoopObserver 0x60400013a360 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x10a079e01), context = <CFRunLoopObserver context 0x7f8c1ac00d60>}
        13 : <CFRunLoopSource 0x604000168340 [0x109e9cc80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6040000be960, callout = FBSSerialQueueRunLoopSourceHandler (0x10e4ac82f)}}
        14 : <CFRunLoopSource 0x604000168880 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 43275, subsystem = 0x10b1c3fe8, context = 0x0}}
        15 : <CFRunLoopSource 0x604000168f40 [0x109e9cc80]>{signalled = Yes, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x604000258600, callout = __handleHIDEventFetcherDrain (0x10a9a7a8e)}}
        16 : <CFRunLoopSource 0x604000169b40 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 42243, subsystem = 0x10b1de668, context = 0x6000002291e0}}
        17 : <CFRunLoopSource 0x604000169540 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 39943, subsystem = 0x10e8bdf78, context = 0x6040000be780}}
        18 : <CFRunLoopSource 0x600000168b80 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000044f300> {port = 5527, callback = 0x0}}
        19 : <CFRunLoopSource 0x6000001687c0 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000223f80> {port = 3c17, callback = 0x121f79643}}
        20 : <CFRunLoopSource 0x604000168c40 [0x109e9cc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000149b60, callout = __handleEventQueue (0x10a9a7a82)}}
    }
    ,
    modes = <CFBasicHash 0x600000245b50 [0x109e9cc80]>{type = mutable set, count = 4,
    entries =>
        2 : <CFRunLoopMode 0x600000188200 [0x109e9cc80]>{name = UITrackingRunLoopMode, port set = 0x1b03, queue = 0x600000149ab0, source = 0x6000001882d0 (not fired), timer port = 0x5403, 
        sources0 = <CFBasicHash 0x604000258e10 [0x109e9cc80]>{type = mutable set, count = 4,
    entries =>
        0 : <CFRunLoopSource 0x600000167800 [0x109e9cc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10ed3e75a)}}
        1 : <CFRunLoopSource 0x604000168c40 [0x109e9cc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000149b60, callout = __handleEventQueue (0x10a9a7a82)}}
        2 : <CFRunLoopSource 0x604000168340 [0x109e9cc80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6040000be960, callout = FBSSerialQueueRunLoopSourceHandler (0x10e4ac82f)}}
        3 : <CFRunLoopSource 0x604000168f40 [0x109e9cc80]>{signalled = Yes, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x604000258600, callout = __handleHIDEventFetcherDrain (0x10a9a7a8e)}}
    }
    ,
        sources1 = <CFBasicHash 0x604000258d80 [0x109e9cc80]>{type = mutable set, count = 6,
    entries =>
        1 : <CFRunLoopSource 0x604000168040 [0x109e9cc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2d03, callout = PurpleEventCallback (0x10ed40bf7)}}
        2 : <CFRunLoopSource 0x600000168b80 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000044f300> {port = 5527, callback = 0x0}}
        3 : <CFRunLoopSource 0x604000168880 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 43275, subsystem = 0x10b1c3fe8, context = 0x0}}
        4 : <CFRunLoopSource 0x6000001687c0 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000223f80> {port = 3c17, callback = 0x121f79643}}
        5 : <CFRunLoopSource 0x604000169b40 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 42243, subsystem = 0x10b1de668, context = 0x6000002291e0}}
        6 : <CFRunLoopSource 0x604000169540 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 39943, subsystem = 0x10e8bdf78, context = 0x6040000be780}}
    }
    ,
        observers = (
        "<CFRunLoopObserver 0x60400013a540 [0x109e9cc80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10a04adf2), context = <CFArray 0x604000456950 [0x109e9cc80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f8c1b008048>\n)}}",
        "<CFRunLoopObserver 0x604000139aa0 [0x109e9cc80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x10a63057f), context = <CFRunLoopObserver context 0x6040000cc940>}",
        "<CFRunLoopObserver 0x60400013a360 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x10a079e01), context = <CFRunLoopObserver context 0x7f8c1ac00d60>}",
        "<CFRunLoopObserver 0x60000013a360 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10fb70672), context = <CFRunLoopObserver context 0x0>}",
        "<CFRunLoopObserver 0x60400013a720 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x10a079e7c), context = <CFRunLoopObserver context 0x7f8c1ac00d60>}",
        "<CFRunLoopObserver 0x60400013a5e0 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10a04adf2), context = <CFArray 0x604000456950 [0x109e9cc80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f8c1b008048>\n)}}"
    ),
        timers = (null),
        currently 562869526 (825889558479316) / soft deadline in: 1.84459182e+10 sec (@ -1) / hard deadline in: 1.84459182e+10 sec (@ -1)
    },
    
        3 : <CFRunLoopMode 0x604000188610 [0x109e9cc80]>{name = GSEventReceiveRunLoopMode, port set = 0x5303, queue = 0x604000149e20, source = 0x6040001886e0 (not fired), timer port = 0x2c03, 
        sources0 = <CFBasicHash 0x604000258f00 [0x109e9cc80]>{type = mutable set, count = 1,
    entries =>
        0 : <CFRunLoopSource 0x600000167800 [0x109e9cc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10ed3e75a)}}
    }
    ,
        sources1 = <CFBasicHash 0x604000258f30 [0x109e9cc80]>{type = mutable set, count = 1,
    entries =>
        0 : <CFRunLoopSource 0x604000168100 [0x109e9cc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2d03, callout = PurpleEventCallback (0x10ed40bf7)}}
    }
    ,
        observers = (null),
        timers = (null),
        currently 562869526 (825889560343690) / soft deadline in: 1.84459182e+10 sec (@ -1) / hard deadline in: 1.84459182e+10 sec (@ -1)
    },
    
        4 : <CFRunLoopMode 0x600000187c50 [0x109e9cc80]>{name = kCFRunLoopDefaultMode, port set = 0x2403, queue = 0x6000001498a0, source = 0x600000187b80 (not fired), timer port = 0x2303, 
        sources0 = <CFBasicHash 0x604000258ea0 [0x109e9cc80]>{type = mutable set, count = 4,
    entries =>
        0 : <CFRunLoopSource 0x600000167800 [0x109e9cc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x10ed3e75a)}}
        1 : <CFRunLoopSource 0x604000168c40 [0x109e9cc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000149b60, callout = __handleEventQueue (0x10a9a7a82)}}
        2 : <CFRunLoopSource 0x604000168340 [0x109e9cc80]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6040000be960, callout = FBSSerialQueueRunLoopSourceHandler (0x10e4ac82f)}}
        3 : <CFRunLoopSource 0x604000168f40 [0x109e9cc80]>{signalled = Yes, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x604000258600, callout = __handleHIDEventFetcherDrain (0x10a9a7a8e)}}
    }
    ,
        sources1 = <CFBasicHash 0x604000258e70 [0x109e9cc80]>{type = mutable set, count = 6,
    entries =>
        1 : <CFRunLoopSource 0x604000168040 [0x109e9cc80]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2d03, callout = PurpleEventCallback (0x10ed40bf7)}}
        2 : <CFRunLoopSource 0x600000168b80 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000044f300> {port = 5527, callback = 0x0}}
        3 : <CFRunLoopSource 0x604000168880 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 43275, subsystem = 0x10b1c3fe8, context = 0x0}}
        4 : <CFRunLoopSource 0x6000001687c0 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000223f80> {port = 3c17, callback = 0x121f79643}}
        5 : <CFRunLoopSource 0x604000169b40 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 42243, subsystem = 0x10b1de668, context = 0x6000002291e0}}
        6 : <CFRunLoopSource 0x604000169540 [0x109e9cc80]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 39943, subsystem = 0x10e8bdf78, context = 0x6040000be780}}
    }
    ,
        observers = (
        "<CFRunLoopObserver 0x60400013a540 [0x109e9cc80]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10a04adf2), context = <CFArray 0x604000456950 [0x109e9cc80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f8c1b008048>\n)}}",
        "<CFRunLoopObserver 0x604000139aa0 [0x109e9cc80]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x10a63057f), context = <CFRunLoopObserver context 0x6040000cc940>}",
        "<CFRunLoopObserver 0x60400013a360 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x10a079e01), context = <CFRunLoopObserver context 0x7f8c1ac00d60>}",
        "<CFRunLoopObserver 0x60000013a360 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x10fb70672), context = <CFRunLoopObserver context 0x0>}",
        "<CFRunLoopObserver 0x60400013a720 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x10a079e7c), context = <CFRunLoopObserver context 0x7f8c1ac00d60>}",
        "<CFRunLoopObserver 0x60400013a5e0 [0x109e9cc80]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10a04adf2), context = <CFArray 0x604000456950 [0x109e9cc80]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f8c1b008048>\n)}}"
    ),
        timers = <CFArray 0x6000000bdee0 [0x109e9cc80]>{type = mutable-small, count = 1, values = (
        0 : <CFRunLoopTimer 0x604000168b80 [0x109e9cc80]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 562869525 (-1.36409605 @ 825888200199189), callout = (NSTimer) [_UISystemGestureGateGestureRecognizer _timeOut] (0x108cd348a / 0x10a61169e) (/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/Library/CoreSimulator/Profiles/Runtimes/iOS.simruntime/Contents/Resources/RuntimeRoot/System/Library/Frameworks/UIKit.framework/UIKit), context = <CFRunLoopTimer context 0x60400043cb40>}
    )},
        currently 562869526 (825889560397419) / soft deadline in: 1.84467441e+10 sec (@ 825888200199189) / hard deadline in: 1.84467441e+10 sec (@ 825888200199189)
    },
    
        5 : <CFRunLoopMode 0x604000189720 [0x109e9cc80]>{name = kCFRunLoopCommonModes, port set = 0x4713, queue = 0x60400014a7c0, source = 0x604000189580 (not fired), timer port = 0xa80f, 
        sources0 = (null),
        sources1 = (null),
        observers = (null),
        timers = (null),
        currently 562869526 (825889564441067) / soft deadline in: 1.84459182e+10 sec (@ -1) / hard deadline in: 1.84459182e+10 sec (@ -1)
    },
    
    
    

    信息量有点多,日志中RunLoop大概包含current mode、common modes、common mode items、modes这些信息。实际上从开源的文档里,RunLoop包含的成员远不止上面这些,具体包含内容如下图:

    RunLoop结构体成员.png

    其中比较重要的是"_modes"这个成员,它是一个集合类型,里面存放了多个CFRunLoopMode类型的mode成员,一个RunLoop可以有多个mode,这些mode都包含在"_modes"里。CFRunLoopMode结构体如下:

    CFRunLoopMode 结构体.png

    CFRunLoopModeRef是什么?

    • CFRunLoopModeRef代表RunLoop的运行模式;
    • 一个RunLoop包含若干个Mode ,每个Mode又包含若干个Source0/Source1/Timer/Observer;
    • RunLoop启动时只能选择其中一个Mode ,作为currentMode;
    • 如果需要切换Mode ,只能退出当前Loop ,再重新选择一个Mode进入;
    • 不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响;
    • 如果Mode里没有Source0/Source1/Timer, RunLoop会立马退出(很多人说没有Observer也会退出,但是我看源码上面并没有对Observer的存在做任何判断,源码如下:)。
    static Boolean __CFRunLoopModeIsEmpty(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopModeRef previousMode) {
        CHECK_FOR_FORK();
        if (NULL == rlm) return true;
    #if DEPLOYMENT_TARGET_WINDOWS
        if (0 != rlm->_msgQMask) return false;
    #endif
        Boolean libdispatchQSafe = pthread_main_np() && ((HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode) || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ)));
        if (libdispatchQSafe && (CFRunLoopGetMain() == rl) && CFSetContainsValue(rl->_commonModes, rlm->_name)) return false; // represents the libdispatch main queue
        //如果没有注入sources0 sources1 或timers,就会退出RunLoop。
        if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false;
        if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false;
        if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false;
        struct _block_item *item = rl->_blocks_head;
        while (item) {
            struct _block_item *curr = item;
            item = item->_next;
            Boolean doit = false;
            if (CFStringGetTypeID() == CFGetTypeID(curr->_mode)) {
                doit = CFEqual(curr->_mode, rlm->_name) || (CFEqual(curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
            } else {
                doit = CFSetContainsValue((CFSetRef)curr->_mode, rlm->_name) || (CFSetContainsValue((CFSetRef)curr->_mode, kCFRunLoopCommonModes) && CFSetContainsValue(rl->_commonModes, rlm->_name));
            }
            if (doit) return false;
        }
        return true;
    }
    

    中间三段“return false;”的判断清晰地说明了runloop退出的条件。
    if (NULL != rlm->_sources0 && 0 < CFSetGetCount(rlm->_sources0)) return false;
    if (NULL != rlm->_sources1 && 0 < CFSetGetCount(rlm->_sources1)) return false;
    if (NULL != rlm->_timers && 0 < CFArrayGetCount(rlm->_timers)) return false;

    RunLoop与mode关系.png

    RunLoop常见的两种Mode

    • kCFRunLoopDefaultMode ( NSDefaultRunLoopMode) : App默认的Mode , 通常主线程会在这个模式下运行。
    • UITrackingRunLoopMode : 界面跟踪Mode , 用于 ScrollView 追踪界面滑动事件,保证界面滑动不受其他mode的影响。

    每一种mode里面都有source0、source1、observers、timers,他们主要负责的内容如下:

    Source0

    • 触摸事件处理
    • performSelector:onThread:

    Source1

    • 基于Port的线程间通信
    • 系统事件捕捉

    Timers

    • NSTimer
    • performSelector:withobiect:afterDelay;

    Observers

    • 用于监听Runloop的状态
    • UI刷新( BeforeWaiting)
    • Autorelease pool ( BeforeWaiting )

    两种方式监听RunLoop状态:

    方式①:
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        // 创建Observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            
            CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
            switch (activity) {
                case kCFRunLoopEntry: {
                    NSLog(@"kCFRunLoopEntry - %@", mode);
                    CFRelease(mode);
                    break;
                }
                case kCFRunLoopBeforeTimers:{
                    NSLog(@"kCFRunLoopBeforeTimers - %@", mode);
                      CFRelease(mode);
                    break;
                }
                case kCFRunLoopBeforeSources:{
                    NSLog(@"kCFRunLoopBeforeSources - %@", mode);
                      CFRelease(mode);
                    break;
                }
                case kCFRunLoopBeforeWaiting:{
                    NSLog(@"kCFRunLoopBeforeWaiting - %@", mode);
                      CFRelease(mode);
                    break;
                }
                case kCFRunLoopAfterWaiting:{
                    NSLog(@"kCFRunLoopAfterWaiting - %@", mode);
                      CFRelease(mode);
                    break;
                }
                case kCFRunLoopExit: {
                    NSLog(@"kCFRunLoopExit - %@", mode);
                    CFRelease(mode);
                    break;
                }
                    
                default:
                    break;
            }
        });
        // 添加Observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放
        CFRelease(observer);
    }
    
    
    方式②:
    void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
    {
        CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
        switch (activity) {
            case kCFRunLoopEntry:
                 NSLog(@"kCFRunLoopEntry - %@", mode);
                break;
            case kCFRunLoopBeforeTimers:
                 NSLog(@"kCFRunLoopBeforeTimers - %@", mode);
                break;
            case kCFRunLoopBeforeSources:
                 NSLog(@"kCFRunLoopBeforeSources - %@", mode);
                break;
            case kCFRunLoopBeforeWaiting:
                 NSLog(@"kCFRunLoopBeforeWaiting - %@", mode);
                break;
            case kCFRunLoopAfterWaiting:
                 NSLog(@"kCFRunLoopAfterWaiting - %@", mode);
                break;
            case kCFRunLoopExit:
                 NSLog(@"kCFRunLoopExit - %@", mode);
                break;
            default:
                break;
        }
         CFRelease(mode);
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        // kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
        
        // 创建Observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
        // 添加Observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放
        CFRelease(observer);
    }
    
    

    RunLoop运行逻辑

    RunLoop的运行逻辑是怎么样的?让我们从一次熟悉的触摸事件开始分析吧O(∩_∩)O~~

    触摸事件函数调用栈信息.png

    为方便查看- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event调用的堆栈信息,特意将堆栈信息提取出来:

    (lldb) bt
    * thread #1, queue = 'com.apple.main-thread', stop reason = breakpoint 1.1
      * frame #0: 0x000000010401968d RunLoop_SearchTest`-[ViewController touchesBegan:withEvent:](self=0x00007faa9ee66310, _cmd="touchesBegan:withEvent:", touches=1 element, event=0x000060000010dd10) at ViewController.m:28
        frame #1: 0x00000001059307c7 UIKit`forwardTouchMethod + 340
        frame #2: 0x0000000105930662 UIKit`-[UIResponder touchesBegan:withEvent:] + 49
        frame #3: 0x0000000105778e7a UIKit`-[UIWindow _sendTouchesForEvent:] + 2052
        frame #4: 0x000000010577a821 UIKit`-[UIWindow sendEvent:] + 4086
        frame #5: 0x000000010571e370 UIKit`-[UIApplication sendEvent:] + 352
        frame #6: 0x000000012211fe6b UIKit`-[UIApplicationAccessibility sendEvent:] + 85
        frame #7: 0x000000010605f57f UIKit`__dispatchPreprocessedEventFromEventQueue + 2796
        frame #8: 0x0000000106062194 UIKit`__handleEventQueueInternal + 5949
        frame #9: 0x0000000105228bb1 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
        frame #10: 0x000000010520d4af CoreFoundation`__CFRunLoopDoSources0 + 271
        frame #11: 0x000000010520ca6f CoreFoundation`__CFRunLoopRun + 1263
        frame #12: 0x000000010520c30b CoreFoundation`CFRunLoopRunSpecific + 635
        frame #13: 0x000000010a3f1a73 GraphicsServices`GSEventRunModal + 62
        frame #14: 0x00000001057030b7 UIKit`UIApplicationMain + 159
        frame #15: 0x000000010401977f RunLoop_SearchTest`main(argc=1, argv=0x00007ffeebbe5fc8) at main.m:15
        frame #16: 0x0000000108cdf955 libdyld.dylib`start + 1
        frame #17: 0x0000000108cdf955 libdyld.dylib`start + 1
    
    

    从后往前看:frame#17、frame#16是系统的动态库加载过程;
    frame#15开始进入应用的main.m文件,指向了Line 15,也就是return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));,也就是创建了一个主运行循环,让程序保持运行状态不退出。从frame #12开始进入了RunLoop的入口,也就是CoreFoundation框架下的CFRunLoopRunSpecific函数,定位到CFRunLoop源码中:

    CFRunLoopRunSpecific函数.png

    提取出红色区域最关键的三行代码(也是CFRunLoopRunSpecific函数的核心):

    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry);
    result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode);
    __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    也就是说一开始通知Observers进入Loop;
    中间RunLoop都是在不停地run;
    最后通知Observers退出Loop。
    显然最重要的就是中间的过程②,也就是__CFRunLoopRun函数。我们继续往这个函数里面看:
    CFRunLoop.c源码中,从2331~2647行都是这个函数的实现部分,实在太长了。。。(鉴于函数里做了大量的平台判断,删除Windows平台和无关条件代码后,其实也就200行吧:),等下将精简版放出来,别扔鸡蛋就OK)

    /* rl, rlm are locked on entrance and exit */
    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) {
    
        Boolean didDispatchPortLastTime = true;
        int32_t retVal = 0;
        do {
            voucher_mach_msg_state_t voucherState = VOUCHER_MACH_MSG_STATE_UNCHANGED;
            voucher_t voucherCopy = NULL;
    
            uint8_t msg_buffer[3 * 1024];
    
            mach_msg_header_t *msg = NULL;
            mach_port_t livePort = MACH_PORT_NULL;
    
            __CFPortSet waitSet = rlm->_portSet;
    
            __CFRunLoopUnsetIgnoreWakeUps(rl);
    //        通知Observers,即将处理Timers
            if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers);
    //        通知Observers,即将处理Sources
            if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources);
    //       处理block
            __CFRunLoopDoBlocks(rl, rlm);
    //       处理source0事件
            Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle);
            if (sourceHandledThisLoop) {
                //如果条件成立会再一次处理block事件
                __CFRunLoopDoBlocks(rl, rlm);
        }
    
            Boolean poll = sourceHandledThisLoop || (0ULL == timeout_context->termTSR);
    
            if (MACH_PORT_NULL != dispatchPort && !didDispatchPortLastTime) {
    
                msg = (mach_msg_header_t *)msg_buffer;
                //涉及到Port,也就是Source1,如果存在Source1事件,就跳转到“handle_msg”代码块
                if (__CFRunLoopServiceMachPort(dispatchPort, &msg, sizeof(msg_buffer), &livePort, 0, &voucherState, NULL)) {
                    goto handle_msg;
                }
            }
    
            didDispatchPortLastTime = false;
        //通知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);
            
            __CFRunLoopModeUnlock(rlm);
            __CFRunLoopUnlock(rl);
    
            CFAbsoluteTime sleepStart = poll ? 0.0 : CFAbsoluteTimeGetCurrent();
    
            //等待别的消息来唤醒线程
            __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY, &voucherState, &voucherCopy);
            
            
            __CFRunLoopLock(rl);
            __CFRunLoopModeLock(rlm);
    
            rl->_sleepTime += (poll ? 0.0 : (CFAbsoluteTimeGetCurrent() - sleepStart));
    
            // Must remove 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. Also, we don't want them left
            // in there if this function returns.
    
            __CFPortSetRemove(dispatchPort, waitSet);
            
            __CFRunLoopSetIgnoreWakeUps(rl);
    
            // user callouts now OK again
            __CFRunLoopUnsetSleeping(rl);
        // 通知Observers,线程被唤醒
        if (!poll && (rlm->_observerMask & kCFRunLoopAfterWaiting)) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting);
    
            handle_msg:;
            __CFRunLoopSetIgnoreWakeUps(rl);
    
            //查看线程是怎么被唤醒的。
    
            if (MACH_PORT_NULL == livePort) {
                CFRUNLOOP_WAKEUP_FOR_NOTHING();
                // handle nothing
            } else if (livePort == rl->_wakeUpPort) {
                CFRUNLOOP_WAKEUP_FOR_WAKEUP();
                // do nothing on Mac OS
            }
    
            else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) {
                //处理Timer事件
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    // Re-arm the next timer, because we apparently fired early
                    __CFArmNextTimerInMode(rlm, rl);
                }
            }
    
            else if (rlm->_timerPort != MACH_PORT_NULL && livePort == rlm->_timerPort) {
                 //处理Timer事件
                CFRUNLOOP_WAKEUP_FOR_TIMER();
                // On Windows, we have observed an issue where the timer port is set before the time which we requested it to be set. For example, we set the fire time to be TSR 167646765860, but it is actually observed firing at TSR 167646764145, which is 1715 ticks early. The result is that, when __CFRunLoopDoTimers checks to see if any of the run loop timers should be firing, it appears to be 'too early' for the next timer, and no timers are handled.
                // In this case, the timer port has been automatically reset (since it was returned from MsgWaitForMultipleObjectsEx), and if we do not re-arm it, then no timers will ever be serviced again unless something adjusts the timer list (e.g. adding or removing timers). The fix for the issue is to reset the timer here if CFRunLoopDoTimers did not handle a timer itself. 9308754
                if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) {
                    // Re-arm the next timer
                    __CFArmNextTimerInMode(rlm, rl);
                }
            }
    
            else if (livePort == dispatchPort) {
                 //处理GCD相关事件
                CFRUNLOOP_WAKEUP_FOR_DISPATCH();
                __CFRunLoopModeUnlock(rlm);
                __CFRunLoopUnlock(rl);
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)6, NULL);
    
                __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
                _CFSetTSD(__CFTSDKeyIsInGCDMainQ, (void *)0, NULL);
                __CFRunLoopLock(rl);
                __CFRunLoopModeLock(rlm);
                sourceHandledThisLoop = true;
                didDispatchPortLastTime = true;
            } else {
                 //处理Source1事件
                CFRUNLOOP_WAKEUP_FOR_SOURCE();
                
                // If we received a voucher from this mach_msg, then put a copy of the new voucher into TSD. CFMachPortBoost will look in the TSD for the voucher. By using the value in the TSD we tie the CFMachPortBoost to this received mach_msg explicitly without a chance for anything in between the two pieces of code to set the voucher again.
                voucher_t previousVoucher = _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, (void *)voucherCopy, os_release);
    
                // Despite the name, this works for windows handles as well
                CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort);
                if (rls) {
                    mach_msg_header_t *reply = NULL;
                    sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop;
                    if (NULL != reply) {
                        (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL);
                        CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply);
                    }
    
            }
                
                // Restore the previous voucher
                _CFSetTSD(__CFTSDKeyMachMessageHasVoucher, previousVoucher, os_release);
                
            } 
    
            if (msg && msg != (mach_msg_header_t *)msg_buffer) free(msg);
            //处理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;
        }
            
        voucher_mach_msg_revert(voucherState);
        os_release(voucherCopy);
    
    
        } while (0 == retVal);
    
        if (timeout_timer) {
            dispatch_source_cancel(timeout_timer);
            dispatch_release(timeout_timer);
        } else {
            free(timeout_context);
        }
    
        return retVal;
    }
    
    RunLoop运行逻辑.png

    RunLoop休眠的实现
    根据源码中“ __CFRunLoopRun”函数的实现细节,RunLoop在处理完Timers、Observers、Blocks,会检查一遍是否有Source1,如果没有的话就会进入休眠状态。“休眠状态”下,App不会进行任何动作,这近乎一种‘死亡’状态,看起来又像是‘卡住了’,但是与while(1){};循环所表现的不一样的是,这个时候当前线程不会处理任何事情,也不会阻塞,不占用CPU资源。系统能做到这样,得益于一个内核层面的API,即mach_msg(msg, MACH_RCV_MSG|(voucherState ? MACH_RCV_VOUCHER : 0)|MACH_RCV_LARGE|((TIMEOUT_INFINITY != timeout) ? MACH_RCV_TIMEOUT : 0)|MACH_RCV_TRAILER_TYPE(MACH_MSG_TRAILER_FORMAT_0)|MACH_RCV_TRAILER_ELEMENTS(MACH_RCV_TRAILER_AV), 0, msg->msgh_size, port, timeout, MACH_PORT_NULL);,RunLoop 的核心就是一个 mach_msg() ,RunLoop 调用mach_msg()这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态。

    RunLoop休眠的核心原理.png 看到这里,点个赞呗❤️.png

    相关文章

      网友评论

          本文标题:iOS RunLoop底层探索

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