美文网首页iOS 开发 iOS开发iOS进阶之路
RunLoop(从源码分析到Demo分析到mainLoop lo

RunLoop(从源码分析到Demo分析到mainLoop lo

作者: 轶匠 | 来源:发表于2018-03-14 23:38 被阅读1208次

    RunLoop的概念

    首先我们通过Xcode创建一个Command Line Tool project。发现Xcode给我们自动生成了main.m,包含如下代码:

    #import <Foundation/Foundation.h>
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    

    运行后,控制台输出Hello,World!然后程序就退出了。

    然而,我们在创建的iOS程序,main.m中包含的代码如下:

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        }
    }
    

    运行后,程序并不会退出,而是一直处于运行状态,等待用户响应。当我们把main函数稍作修改,如下:

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            int i = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
            return i;
        }
    }
    

    return i;这一行加上断点。发现并不会被断点断住。说明并没有执行到这一步,即上一步还没有结束。为什么会这样呢,那就引出了今天的主角:RunLoop!因为UIApplicationMain函数内部帮我创建了一个RunLoop “运行循环”,来保证线程不会退出,能随时处理事件和消息。大致的代码逻辑:

    function loop() {
        initialize();
        do {
            var message = get_next_message();
            process_message(message);
        } while (message != quit);
    }
    

    会有一个do while循环来等待message,并处理message,只有当while条件不满足时(比如传入 quit 的消息),才会退出循环,让函数返回。而RunLoop内对其进行了进一步的优化:它能很好的管理事件和消息,并且让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。下文会对其进行讲解。

    在继续之前,我们可以先下载CFRunLoopRef源码,来进行更好的分析和学习。下载地址:https://opensource.apple.com/tarballs/CF/

    通过CoreFoundation框架和Foundation框架,我们发现OSX/iOS 系统中,提供了两个这样的对象:CFRunLoopRef 和 NSRunLoop。

    CFRunLoopRef提供了纯C语言的api,所以是线程安全。而NSRunLoop是基于CFRunLoopRef的封装,符合了面向对象的特点,但是不是线程安全的。

    RunLoop的创建过程

    CFRunLoop.c文件的部分源代如下:

    static CFMutableDictionaryRef __CFRunLoops = NULL;
    static pthread_t kNilPthreadT = (pthread_t)0;
    
    
    // t==0 is a synonym for "main thread" that always works //当t==0时代表主线程
    CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
        }
        //进行加锁操作
        __CFLock(&loopsLock);
        if (!__CFRunLoops) {
            __CFUnlock(&loopsLock);
        // 第一次进入时,初始化全局dict,并先为主线程创建一个 RunLoop。并将mainLoop添加到dict中
        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);
        }
        //通过线程直接从dict中获取loop
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
        if (!loop) {
        //如果获取失败,通过线程创建一个loop,
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            //再次确认没有loop,就添加到dict中。
            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())) {
            // 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
            _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
            if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
                _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
            }
        }
        return loop;
    }
    
    //获取当前线程的RunLoop
    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK();
        CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
        if (rl) return rl;
        return _CFRunLoopGet0(pthread_self());
    }
    
    //获取主线程的RunLoop
    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;
    }
    

    整体的流程可以概括为以下几步:

    • 通过_CFRunLoopGet0函数传入一条线程。
    • 判断线程是否为主线程并且判断是否已经存在__CFRunLoops(全局CFMutableDictionaryRef)。
    • 如果不存在,说明第一次进入,初始化全局dict,并先为主线程创建一个 RunLoop。并将mainLoop添加到dict中。
    • 如果__CFRunLoops存在,会通过对应线程在全局的__CFRunLoops中查找对应的RunLoop。
    • 如果对应RunLoop不存在,会创建一个新的RunLoop,并添加到__CFRunLoops中。
    • 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop。
    • 返回RunLoop。

    我们只能通过CFRunLoopGetMain函数或者CFRunLoopGetCurrent函数来获取RunLoop,通过上面的源代码我们发现,无论是CFRunLoopGetMain函数还是CFRunLoopGetCurrent函数,都是通过对应的线程获取对应的RunLoop,线程和RunLoop是一一对应的,不会重复创建。在主线程,系统会帮我们创建RunLoop,来处理事件。而子线程RunLoop并不会默认开启。所有,子线程操作完成后,线程就被销毁了,如果我们想线程不被销毁,得主动获取一个RunLoop,并且在RunLoop中添加Timer/Source/Observer其中的一个。

    RunLoop的内部结构

    在 CoreFoundation 里面关于 RunLoop 有5个类:

    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopObserverRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef

    CFRunLoopMode 和 CFRunLoop 的源码结构大致如下:

    struct __CFRunLoopMode {
        CFStringRef _name;            // Mode Name, 例如 @"kCFRunLoopDefaultMode"
        CFMutableSetRef _sources0;    // Set
        CFMutableSetRef _sources1;    // Set
        CFMutableArrayRef _observers; // Array
        CFMutableArrayRef _timers;    // Array
        ...
    };
     
    struct __CFRunLoop {
        CFMutableSetRef _commonModes;     // Set
        CFMutableSetRef _commonModeItems; // Set<Source/Observer/Timer>
        CFRunLoopModeRef _currentMode;    // Current Runloop Mode
        CFMutableSetRef _modes;           // Set
        ...
    };
    

    可以看出CFRunLoop和CFRunLoopMode有如下关系:

    RunLoop内部结构图

    每个RunLoop中有若干个Mode,每个Mode中又存在着若干个Observer、Source和Timer,每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

    CFRunLoopObserverRef:是观察者,我们可以通过CFRunLoopObserverCreateWithHandler函数来创建一个观察者(函数会有一个block回调),来对RunLoop进行观察,当RunLoop状态变化时,会触发block回调,回调会返回对应的状态,我们可以在回调里做相应做的操作。可以观察到的状态有:

    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry             = (1UL << 0),// 1 即将进入Loop
        kCFRunLoopBeforeTimers      = (1UL << 1),// 2 即将处理 Timer
        kCFRunLoopBeforeSources     = (1UL << 2),// 4 即将处理 Source
        kCFRunLoopBeforeWaiting     = (1UL << 5),// 32 即将进入休眠
        kCFRunLoopAfterWaiting      = (1UL << 6),// 64 刚从休眠中唤醒
        kCFRunLoopExit              = (1UL << 7),// 128 即将退出Loop
        kCFRunLoopAllActivities     = 0x0FFFFFFFU// 所有状态类型
    };
    

    我们来看下具体的举例(Demo地址)大家不妨下载看看:

    - (void)observer {
        // 创建observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"监听到即将进入RunLoop------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"监听到即将处理Timer------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"监听到即将处理Source------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"监听到即将进入睡眠------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"监听到即将从睡眠中醒来------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
                    break;
                case kCFRunLoopExit:
                    NSLog(@"监听到即将从退出RunLoop------%zd----%@",activity,[[NSRunLoop currentRunLoop] currentMode]);
                    break;
                default:
                    break;
            }
        });
        
        /*
        CFRunLoopAddObserver函数有三个参数:
         * 第一个:传入一个RunLoop,CFRunLoopGetCurrent()获取当前的RunLoop
         * 第二个:传入一个观察者,observer就是新创建的观察者
         * 第三个:传入Mode,kCFRunLoopCommonModes指定要监听的Mode
        */
        
        // 添加观察者到RunLoop:来监听RunLoop的状态
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
        // 释放Observer
        CFRelease(observer);
    }
    
    

    除了在viewDidLoad里面调用了[self observer];方法,还在Main.storyboard中添加了UITextView。下面我们拿打印结果阐述。

    运行后的模拟器截图:

    模拟器截图

    刚刚触发监听的截图:

    刚刚触发监听的截图

    我们发现RunLoop在不停的处理Timer事件和Source事件。虽然我们没有主动添加Timer和Source事件,但是系统会添加Timer和Source,比如:模拟器的时间发生变化时就会触发kCFRunLoopBeforeTimers回调(每分钟),如下图:

    当视图显示完成稳定后无事件的截图(看具体的时间点发现是在整分钟的时候触发的回调):


    稳定后无事件的截图

    当无其他点击等事件时,会进入睡眠状态,有事件时会立刻苏醒过来处理事件。

    拖拽textView时的截图:

    拖拽textView时的截图

    发现在拖拽textView的时候,当前RunLoop的Mode[[NSRunLoop currentRunLoop] currentMode]变成了从kCFRunLoopDefaultMode变成了UITrachingRunloopMode,RunLoop也触发了kCFRunLoopExit(即将退出RunLoop)的回调和kCFRunLoopEntry(即将进入RunLoop)的回调,也就是:退出了当前RunLoop,进入了一个新的RunLoop,也就验证了上面所说的:每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 当前RunLoop,再重新指定一个 Mode 进入。

    如果到这里看不懂,没有关系,后面会对RunLoop 的内部逻辑进行分析。看了后面的分析在回过头来看就能明白了。

    CFRunLoopSourceRef:是事件产生的地方。Source分为Source0 和 Source1。

    • Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 。
    • Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程。

    CFRunLoopTimerRef:基于时间的触发器,CFRunLoopTimerRef是Core Foundation提供的基础定时器,NSTimer则是建立在CFRunLoopTimerRef之上的高层组件。当Timer被加入到RunLoop时,RunLoop会注册对应的时间点,当达到时间时,RunLoop会被唤醒,执行创建Timer时的回调。

    RunLoop 的 Mode

    我们在viewDidLoad中通过下面代码打印当前RunLoop

        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        NSLog(@"%@",runloop);
    

    我们通过打印结果和分析结合来看(可以先到下面看分析,再回到上面找对应的打印结果或者下载demo,在控制栏对照来看。)
    打印结果如下:

    2018-03-13 23:14:28.671838+0800 runloop[30490:55545179] <CFRunLoop 0x6000001f2400 [0x110675bb0]>{wakeup port = 0x1b03, stopped = false, ignoreWakeUps = false, 
    current mode = kCFRunLoopDefaultMode,
    common modes = <CFBasicHash 0x6000002552d0 [0x110675bb0]>{type = mutable set, count = 2,
    entries =>
        0 : <CFString 0x1119f9820 [0x110675bb0]>{contents = "UITrackingRunLoopMode"}
        2 : <CFString 0x11064b7f0 [0x110675bb0]>{contents = "kCFRunLoopDefaultMode"}
    }
    ,
    common mode items = <CFBasicHash 0x60400024e9a0 [0x110675bb0]>{type = mutable set, count = 16,
    entries =>
        0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
        3 : <CFRunLoopObserver 0x60400013f0e0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1161d3648), context = <CFRunLoopObserver context 0x0>}
        4 : <CFRunLoopSource 0x60c0001795c0 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23555, subsystem = 0x1119cb088, context = 0x600000224660}}
        6 : <CFRunLoopSource 0x60c000178c00 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
        7 : <CFRunLoopSource 0x600000179080 [0x110675bb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000bf260, callout = FBSSerialQueueRunLoopSourceHandler (0x114b2d821)}}
        8 : <CFRunLoopSource 0x600000179380 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000003ffc0> {port = 4e1b, callback = 0x127cb11c0}}
        9 : <CFRunLoopObserver 0x60400013ed20 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x110854057), context = <CFRunLoopObserver context 0x7f874c200000>}
        10 : <CFRunLoopObserver 0x60400013ec80 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (
        0 : <0x7f874a802048>
    )}}
        11 : <CFRunLoopObserver 0x60400013edc0 [0x110675bb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (
        0 : <0x7f874a802048>
    )}}
        12 : <CFRunLoopObserver 0x60000013f400 [0x110675bb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x110e121a9), context = <CFRunLoopObserver context 0x6000000dff00>}
        13 : <CFRunLoopObserver 0x60400013fa40 [0x110675bb0]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x11034eeb0), context = <CFRunLoopObserver context 0x6040004454f0>}
        16 : <CFRunLoopSource 0x604000179980 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22027, subsystem = 0x1119b0ce8, context = 0x0}}
        18 : <CFRunLoopSource 0x600000179680 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000240240> {port = 5527, callback = 0x0}}
        20 : <CFRunLoopSource 0x60400017a340 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000159860, callout = __handleEventQueue (0x11118cdcc)}}
        21 : <CFRunLoopObserver 0x60400013e5a0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x110853fdc), context = <CFRunLoopObserver context 0x7f874c200000>}
        22 : <CFRunLoopSource 0x6040001792c0 [0x110675bb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60c000256260, callout = __handleHIDEventFetcherDrain (0x11118cdd8)}}
    }
    ,
    modes = <CFBasicHash 0x600000255420 [0x110675bb0]>{type = mutable set, count = 4,
    entries =>
        2 : <CFRunLoopMode 0x60400019de90 [0x110675bb0]>{name = UITrackingRunLoopMode, port set = 0x2603, queue = 0x604000159910, source = 0x60400019e100 (not fired), timer port = 0x2a03, 
        sources0 = <CFBasicHash 0x60400024e8b0 [0x110675bb0]>{type = mutable set, count = 4,
    entries =>
        0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
        3 : <CFRunLoopSource 0x6040001792c0 [0x110675bb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60c000256260, callout = __handleHIDEventFetcherDrain (0x11118cdd8)}}
        4 : <CFRunLoopSource 0x600000179080 [0x110675bb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000bf260, callout = FBSSerialQueueRunLoopSourceHandler (0x114b2d821)}}
        5 : <CFRunLoopSource 0x60400017a340 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000159860, callout = __handleEventQueue (0x11118cdcc)}}
    }
    ,
        sources1 = <CFBasicHash 0x60400024e9d0 [0x110675bb0]>{type = mutable set, count = 5,
    entries =>
        0 : <CFRunLoopSource 0x60c0001795c0 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23555, subsystem = 0x1119cb088, context = 0x600000224660}}
        1 : <CFRunLoopSource 0x600000179680 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000240240> {port = 5527, callback = 0x0}}
        2 : <CFRunLoopSource 0x60c000178c00 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
        3 : <CFRunLoopSource 0x600000179380 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000003ffc0> {port = 4e1b, callback = 0x127cb11c0}}
        5 : <CFRunLoopSource 0x604000179980 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22027, subsystem = 0x1119b0ce8, context = 0x0}}
    }
    ,
        observers = (
        "<CFRunLoopObserver 0x60400013edc0 [0x110675bb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}",
        "<CFRunLoopObserver 0x60000013f400 [0x110675bb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x110e121a9), context = <CFRunLoopObserver context 0x6000000dff00>}",
        "<CFRunLoopObserver 0x60400013fa40 [0x110675bb0]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x11034eeb0), context = <CFRunLoopObserver context 0x6040004454f0>}",
        "<CFRunLoopObserver 0x60400013e5a0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x110853fdc), context = <CFRunLoopObserver context 0x7f874c200000>}",
        "<CFRunLoopObserver 0x60400013f0e0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1161d3648), context = <CFRunLoopObserver context 0x0>}",
        "<CFRunLoopObserver 0x60400013ed20 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x110854057), context = <CFRunLoopObserver context 0x7f874c200000>}",
        "<CFRunLoopObserver 0x60400013ec80 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}"
    ),
        timers = (null),
        currently 542646869 (1754787729427038) / soft deadline in: 1.84449893e+10 sec (@ -1) / hard deadline in: 1.84449893e+10 sec (@ -1)
    },
    
        3 : <CFRunLoopMode 0x60400019e1d0 [0x110675bb0]>{name = GSEventReceiveRunLoopMode, port set = 0x2c03, queue = 0x6040001599c0, source = 0x60400019e2a0 (not fired), timer port = 0x2e03, 
        sources0 = <CFBasicHash 0x60400024ea60 [0x110675bb0]>{type = mutable set, count = 1,
    entries =>
        0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
    }
    ,
        sources1 = <CFBasicHash 0x60400024ea90 [0x110675bb0]>{type = mutable set, count = 1,
    entries =>
        2 : <CFRunLoopSource 0x604000179440 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
    }
    ,
        observers = (null),
        timers = (null),
        currently 542646869 (1754787749292029) / soft deadline in: 1.84449893e+10 sec (@ -1) / hard deadline in: 1.84449893e+10 sec (@ -1)
    },
    
        4 : <CFRunLoopMode 0x60000019e370 [0x110675bb0]>{name = kCFRunLoopDefaultMode, port set = 0x1c03, queue = 0x6000001595a0, source = 0x60000019e440 (not fired), timer port = 0x1e03, 
        sources0 = <CFBasicHash 0x60400024ea00 [0x110675bb0]>{type = mutable set, count = 4,
    entries =>
        0 : <CFRunLoopSource 0x604000179500 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x0, callout = PurpleEventSignalCallback (0x1153c16c6)}}
        3 : <CFRunLoopSource 0x6040001792c0 [0x110675bb0]>{signalled = No, valid = Yes, order = -2, context = <CFRunLoopSource context>{version = 0, info = 0x60c000256260, callout = __handleHIDEventFetcherDrain (0x11118cdd8)}}
        4 : <CFRunLoopSource 0x600000179080 [0x110675bb0]>{signalled = Yes, valid = Yes, order = 0, context = <CFRunLoopSource context>{version = 0, info = 0x6000000bf260, callout = FBSSerialQueueRunLoopSourceHandler (0x114b2d821)}}
        5 : <CFRunLoopSource 0x60400017a340 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 0, info = 0x604000159860, callout = __handleEventQueue (0x11118cdcc)}}
    }
    ,
        sources1 = <CFBasicHash 0x60400024ea30 [0x110675bb0]>{type = mutable set, count = 5,
    entries =>
        0 : <CFRunLoopSource 0x60c0001795c0 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 23555, subsystem = 0x1119cb088, context = 0x600000224660}}
        1 : <CFRunLoopSource 0x600000179680 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x600000240240> {port = 5527, callback = 0x0}}
        2 : <CFRunLoopSource 0x60c000178c00 [0x110675bb0]>{signalled = No, valid = Yes, order = -1, context = <CFRunLoopSource context>{version = 1, info = 0x2f03, callout = PurpleEventCallback (0x1153c3bef)}}
        3 : <CFRunLoopSource 0x600000179380 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <MSHRunLoopSource 0x60000003ffc0> {port = 4e1b, callback = 0x127cb11c0}}
        5 : <CFRunLoopSource 0x604000179980 [0x110675bb0]>{signalled = No, valid = Yes, order = 0, context = <CFRunLoopSource MIG Server> {port = 22027, subsystem = 0x1119b0ce8, context = 0x0}}
    }
    ,
        observers = (
        "<CFRunLoopObserver 0x60400013edc0 [0x110675bb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}",
        "<CFRunLoopObserver 0x60000013f400 [0x110675bb0]>{valid = Yes, activities = 0x20, repeats = Yes, order = 0, callout = _UIGestureRecognizerUpdateObserver (0x110e121a9), context = <CFRunLoopObserver context 0x6000000dff00>}",
        "<CFRunLoopObserver 0x60400013fa40 [0x110675bb0]>{valid = Yes, activities = 0x4, repeats = No, order = 0, callout = _runLoopObserverWithBlockContext (0x11034eeb0), context = <CFRunLoopObserver context 0x6040004454f0>}",
        "<CFRunLoopObserver 0x60400013e5a0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 1999000, callout = _beforeCACommitHandler (0x110853fdc), context = <CFRunLoopObserver context 0x7f874c200000>}",
        "<CFRunLoopObserver 0x60400013f0e0 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2000000, callout = _ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv (0x1161d3648), context = <CFRunLoopObserver context 0x0>}",
        "<CFRunLoopObserver 0x60400013ed20 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2001000, callout = _afterCACommitHandler (0x110854057), context = <CFRunLoopObserver context 0x7f874c200000>}",
        "<CFRunLoopObserver 0x60400013ec80 [0x110675bb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x110824276), context = <CFArray 0x60400025e780 [0x110675bb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f874a802048>\n)}}"
    ),
        timers = <CFArray 0x6000002a00c0 [0x110675bb0]>{type = mutable-small, count = 2, values = (
        0 : <CFRunLoopTimer 0x600000179980 [0x110675bb0]>{valid = Yes, firing = No, interval = 0, tolerance = 0, next fire date = 542646863 (-5.80513895 @ 1754781946507642), callout = (Delayed Perform) UIApplication _accessibilitySetUpQuickSpeak (0x10f493d9c / 0x110d1c92d) (/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 0x60000026f400>}
        1 : <CFRunLoopTimer 0x60400017adc0 [0x110675bb0]>{valid = Yes, firing = No, interval = 0.5, tolerance = 0, next fire date = 542646863 (-5.657637 @ 1754782094433008), callout = (NSTimer) [UITextSelectionView caretBlinkTimerFired:] (0x10f4a8acb / 0x10f1d6536) (/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 0x604000438880>}
    )},
        currently 542646869 (1754787749365050) / soft deadline in: 1.84467441e+10 sec (@ 1754781946507642) / hard deadline in: 1.84467441e+10 sec (@ 1754781946507642)
    },
    
        5 : <CFRunLoopMode 0x60400019ed30 [0x110675bb0]>{name = kCFRunLoopCommonModes, port set = 0x3f0f, queue = 0x604000159bd0, source = 0x60400019eac0 (not fired), timer port = 0x570f, 
        sources0 = (null),
        sources1 = (null),
        observers = (null),
        timers = (null),
        currently 542646869 (1754787752303309) / soft deadline in: 1.84449893e+10 sec (@ -1) / hard deadline in: 1.84449893e+10 sec (@ -1)
    },
    
    }
    }
    
    

    从上往下看发现结果和上面说的CFRunLoopMode 和 CFRunLoop 的源码结构是对应上的:

    • current mode = kCFRunLoopDefaultMode:现在处于kCFRunLoopDefaultMode下。
    • common modes:一个 Mode 可以将自己标记为”Common”属性(通过将其 ModeName 添加到 RunLoop 的 “commonModes” 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 “Common” 标记的所有Mode里。 上面我们可以看到被标记为“Common”属性的有2种Mode(count = 2):
      • UITrackingRunLoopMode
      • kCFRunLoopDefaultMode
    • common mode items:count = 16 ,包含了所有被添加到Mode的Source/Observer/Timer。
    • modes:所有被添加到RunLoop中的RunLoopMode,count = 4,包含了四个Mode如下:
      • name = UITrackingRunLoopMode:scrollView滚动时候会切换到这种Mode,上文讲Observer的具体demo中,在打印Observer监听时,当拖拽textView的时候就会切换到这种Mode。从上面打印的RunLoop里面的UITrackingRunLoopMode,可以看出它有包含了sources0(count = 4包含了4个Source0)/sources1(count = 5包含了5个Source1)/observers(7个Observer)/timers(没有Timer)。
      • name = GSEventReceiveRunLoopMode: 苹果接受系统事件的内部 Mode,通常用不到。从上面打印的RunLoop里面的GSEventReceiveRunLoopMode,可以看出它仅仅包含了一个Source0和一个Source1。
      • name = kCFRunLoopDefaultMode:App的默认 Mode,通常主线程是在这个 Mode 下运行的,从上面打印的RunLoop里面的kCFRunLoopDefaultMode,可以看出它包含了4个Source0、5个Source1、7个Observer和2个Timer。
      • name = kCFRunLoopCommonModes:可以看到它里面sources0/sources1/observers/timers都是null,可见这是一个占位的 Mode,没有实际作用。
      • 其实还有一种Mode,这里没有被添加到当前的RunLoop UIInitializationRunLoopMode:它在刚启动 App 时进入的第一个 Mode,启动完成后就不再使用。所以在这里看不到这个Mode。

    在上文讲Observer的具体demo中,有如下代码:

    // 添加观察者到RunLoop:来监听RunLoop的状态
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
    

    CFRunLoopAddObserver函数有三个参数:

    • 第一个:传入一个RunLoop,CFRunLoopGetCurrent()获取当前的RunLoop
    • 第二个:传入一个观察者,observer就是新创建的观察者
    • 第三个:传入Mode,kCFRunLoopCommonModes指定要监听的Mode

    其中有个kCFRunLoopCommonModes就是指被标记成“Common”的Mode,上面打印和分析发现,其实就是指UITrackingRunLoopModekCFRunLoopDefaultMode这两种Mode。所以在拖拽textView的时候和不拖拽的时候都能收到回调,如果将kCFRunLoopCommonModes改为kCFRunLoopDefaultMode,会发现,当拖拽的时候收不到回调。大家可以自己试试。

    和将NSTimer添加到RunLoop时,指定Mode回调是一样的道理,这里就不再阐述了。已经够啰嗦了。。。

    AutoreleasePool的创建和释放时机

    在上面对RunLoop的打印中,我们发现系统添加了两个Observer

    • <CFRunLoopObserver 0x60400013edc0>:activities = 0x1, repeats = Yes, order = -2147483647 它的activities = 0x1,而0x1转换为10进制1,就是表示监听的kCFRunLoopEntry
    • <CFRunLoopObserver 0x60400013ec80>:activities = 0xa0, repeats = Yes, order = 2147483647 它的activities = 0xa0,而0xa0转换为10进制160,发现没有160这个值的状态,其实它是kCFRunLoopBeforeWaiting | kCFRunLoopExit,也就是32+128

    它们都触发了_wrapRunLoopWithAutoreleasePoolHandler回调。

    第一个Observer监听kCFRunLoopEntry(即将进入RunLoop)其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池,其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

    第二个 Observer 监视了kCFRunLoopBeforeWaiting() 其回调内会调用_objc_autoreleasePoolPop()_objc_autoreleasePoolPush() 释放旧的AutoreleasePool并创建新AutoreleasePool;kCFRunLoopExit(即将退出RunLoop) 其回调内会调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

    前几天有写一篇iOS内存管理的文章,里面有讲AutoreleasePool的内部实现。

    RunLoop 的内部逻辑

    根据CoreFoundation框架RunLoop源码,RunLoop 内部的逻辑官方文档如下:

    苹果官方文档RunLoop内部逻辑

    郭大神描述的内部逻辑如下:

    RunLoop内部逻辑

    大体上他们是一致的。

    其内部代码整理如下:

    /// 用DefaultMode启动
    void CFRunLoopRun(void) {
        CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
    }
     
    /// 用指定的Mode启动,允许设置RunLoop超时时间
    int CFRunLoopRunInMode(CFStringRef modeName, CFTimeInterval seconds, Boolean stopAfterHandle) {
        return CFRunLoopRunSpecific(CFRunLoopGetCurrent(), modeName, seconds, returnAfterSourceHandled);
    }
     
    /// RunLoop的实现
    int CFRunLoopRunSpecific(runloop, modeName, seconds, stopAfterHandle) {
        
        /// 首先根据modeName找到对应mode
        CFRunLoopModeRef currentMode = __CFRunLoopFindMode(runloop, modeName, false);
        /// 如果mode里没有source/timer/observer, 直接返回。
        if (__CFRunLoopModeIsEmpty(currentMode)) return;
        
        /// 1. 通知 Observers: RunLoop 即将进入 loop。
        __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopEntry);
        
        /// 内部函数,进入loop
        __CFRunLoopRun(runloop, currentMode, seconds, returnAfterSourceHandled) {
            
            Boolean sourceHandledThisLoop = NO;
            int retVal = 0;
            do {
     
                /// 2. 通知 Observers: RunLoop 即将触发 Timer 回调。
                __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeTimers);
                /// 3. 通知 Observers: RunLoop 即将触发 Source0 (非port) 回调。
                __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeSources);
                /// 执行被加入的block
                __CFRunLoopDoBlocks(runloop, currentMode);
                
                /// 4. RunLoop 触发 Source0 (非port) 回调。
                sourceHandledThisLoop = __CFRunLoopDoSources0(runloop, currentMode, stopAfterHandle);
                /// 执行被加入的block
                __CFRunLoopDoBlocks(runloop, currentMode);
     
                /// 5. 如果有 Source1 (基于port) 处于 ready 状态,直接处理这个 Source1 然后跳转去处理消息。
                if (__Source0DidDispatchPortLastTime) {
                    Boolean hasMsg = __CFRunLoopServiceMachPort(dispatchPort, &msg)
                    if (hasMsg) goto handle_msg;
                }
                
                /// 通知 Observers: RunLoop 的线程即将进入休眠(sleep)。
                if (!sourceHandledThisLoop) {
                    __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopBeforeWaiting);
                }
                
                /// 7. 调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
                /// • 一个基于 port 的Source 的事件。
                /// • 一个 Timer 到时间了
                /// • RunLoop 自身的超时时间到了
                /// • 被其他什么调用者手动唤醒
                __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort) {
                    mach_msg(msg, MACH_RCV_MSG, port); // thread wait for receive msg
                }
     
                /// 8. 通知 Observers: RunLoop 的线程刚刚被唤醒了。
                __CFRunLoopDoObservers(runloop, currentMode, kCFRunLoopAfterWaiting);
                
                /// 收到消息,处理消息。
                handle_msg:
     
                /// 9.1 如果一个 Timer 到时间了,触发这个Timer的回调。
                if (msg_is_timer) {
                    __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
                } 
     
                /// 9.2 如果有dispatch到main_queue的block,执行block。
                else if (msg_is_dispatch) {
                    __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
                } 
     
                /// 9.3 如果一个 Source1 (基于port) 发出事件了,处理这个事件
                else {
                    CFRunLoopSourceRef source1 = __CFRunLoopModeFindSourceForMachPort(runloop, currentMode, livePort);
                    sourceHandledThisLoop = __CFRunLoopDoSource1(runloop, currentMode, source1, msg);
                    if (sourceHandledThisLoop) {
                        mach_msg(reply, MACH_SEND_MSG, reply);
                    }
                }
                
                /// 执行加入到Loop的block
                __CFRunLoopDoBlocks(runloop, currentMode);
                
     
                if (sourceHandledThisLoop && stopAfterHandle) {
                    /// 进入loop时参数说处理完事件就返回。
                    retVal = kCFRunLoopRunHandledSource;
                } else if (timeout) {
                    /// 超出传入参数标记的超时时间了
                    retVal = kCFRunLoopRunTimedOut;
                } else if (__CFRunLoopIsStopped(runloop)) {
                    /// 被外部调用者强制停止了
                    retVal = kCFRunLoopRunStopped;
                } else if (__CFRunLoopModeIsEmpty(runloop, currentMode)) {
                    /// source/timer/observer一个都没有了
                    retVal = kCFRunLoopRunFinished;
                }
                
                /// 如果没超时,mode里没空,loop也没被停止,那继续loop。
            } while (retVal == 0);
        }
        
        /// 10. 通知 Observers: RunLoop 即将退出。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    }
    

    可以看到,实际上 RunLoop 就是这样一个函数,其内部是一个 do-while 循环。当你调用 CFRunLoopRun() 时,线程就会一直停留在这个循环里;直到超时或被手动停止,该函数才会返回。

    PerformSelecter

    当调用 NSObject 的 performSelecter:afterDelay: 后,实际上其内部会创建一个 Timer 并添加到当前线程的 RunLoop 中。所以如果当前线程没有 RunLoop,则这个方法会失效。我们可以在没有获取RunLoop的子线程进行测试验证。viewDidLoad中调用performSelectorTest方法。

    - (void)performSelectorTest {
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
        self.thread = thread;
        [thread start];
    }
    
    - (void)run {
    //    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
    
        [self performSelector:@selector(test) withObject:nil afterDelay:2.0];
    
        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        [runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        [runloop run];
    }
    
    - (void)test {
        NSLog(@"-----------------");
    }
    
    - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
    {
        [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
    }
    
    

    当我们把下面代码注释就不会触发test方法了。

        NSRunLoop *runloop = [NSRunLoop currentRunLoop];
        [runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        [runloop run];
    

    这里有个疑问:
    看郭大神的博客说:performSelector:onThread:也是需要RunLoop,但是我在子线程执行的run 方法中执行[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];而且没有主动获取RunLoop的情况下,也是调用test方法。知道的大神指导下。

    RunLoop其他举例

    UIImageView在NSDefaultRunLoopMode下展示图片

        // 只在NSDefaultRunLoopMode模式下显示图片
        [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"image"] afterDelay:0 inModes:@[NSDefaultRunLoopMode]];
    

    创建常驻子线程

        //在子线程中获取并启动添加了Source的RunLoop
        [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
        [[NSRunLoop currentRunLoop] run];
    

    除了获取当前线程的RunLoop外,还得添加Source或者Timer。并且调用run方法。要不然RunLoop中没有Source和Timer,会直接退出当前RunLoop不会进入循环。

    欢迎指导沟通!


    参考文献:
    官方文档
    深入理解RunLoop
    RunLoop问题集

    相关文章

      网友评论

      • d83b1a9a55db:那个demo里的有个run的实例方法,为什么第一个你注释掉的方法在不添加runloop的情况下依然会触发test方法,求解答
        轶匠:这个只是指定一个线程,执行一个操作。只要线程存在,就可以执行吧。当时我也遇到这个问题。看郭大神的博客说:performSelector:onThread:也是需要RunLoop,但是确实没有获取RunLoop,也是执行了test方法。
      • d83b1a9a55db:kCFRunLoopAfterWaiting 不是即将退出runloop吧,这一点有误吧,应该是线程被唤醒,从休眠中醒来,但是不太清楚这个状态和aotoreleasePoll的关系
        d83b1a9a55db:@一萝卜根儿 还有一个就是我在3楼提的问题,求解答:blush:
        轶匠:谢谢指出,应该是kCFRunLoopExit而不是kCFRunLoopAfterWaiting,我改下。

      本文标题:RunLoop(从源码分析到Demo分析到mainLoop lo

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