• iOS开发:浅尝辄止Runloop


    什么是Runloop

    • 从字面意思就是运行循环
    • 它内部就是do-while循环,在这个循环内部不断地处理各种任务
    • 一个线程对应一个Runloop,主线程的Runloop默认已经启动,子线程的Runloop需要手动启动
    • Runloop只能选择一个Mode启动,如果当前Mode中没有任何Source(Source0、Source1)、Timer,那么直接退出RunLoop
    • 基本作用就是保持程序的持续运行,处理app的各种事件,通过runLoop,有事运行,没事休息,可以节省cpu资源,提高程序性能。
    • OSX/iOS 系统中,提供了两个这样的对象:NSRunLoopCFRunLoopRef
      CFRunLoopRef 是在 CoreFoundation 框架内的,它提供了纯 C 函数的 API,所有这些 API 都是线程安全的。
      NSRunLoop 是基于 CFRunLoopRef 的封装,提供了面向对象的 API,但是这些 API 不是线程安全的。

    Runloop与线程

    • iOS 开发中能遇到两个线程对象: pthread_tNSThreadNSThread 只是 pthread_t 的封装
    • 每条线程都有唯一的一个与之对应的RunLoop对象,其关系是保存在一个全局的 Dictionary 里。
    • 主线程的runloop自动创建,子线程的runloop默认不创建(在子线程中调用NSRunLoop *runloop = [NSRunLoop currentRunLoop]; 获取RunLoop对象的时候,就会创建RunLoop);
    • Runloop寄生于线程:一个线程只能有唯一对应的runloop;但这个根runloop里可以嵌套子runloops;线程刚创建时并没有 RunLoop,如果你不主动获取,那它一直都不会有。RunLoop 的创建是发生在第一次获取时,RunLoop 的销毁是发生在线程结束时。

    RunLoop怎么工作的

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

    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef

    CFRunLoopRef 与 CFRunLoopModeRef

    // 结构体定义
    struct __CFRunLoop {
        pthread_t _pthread;  // runloop和线程是一一对应关系,每个runloop内部会保留一个对应的线程
        CFMutableSetRef _commonModes;  //标记为common的mode的集合
        CFMutableSetRef _commonModeItems;  //commonMode的item集合
        CFRunLoopModeRef _currentMode;  // 当前的模式
        CFMutableSetRef _modes; // CFRunLoopModeRef类型的集合,相对NSArray有序,Set为无序集合
    };
    
    struct __CFRunLoopMode {
        CFStringRef _name;
        CFMutableSetRef _sources0;
        CFMutableSetRef _sources1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 (set) Source/(array)Timer/(array)Observer
    每次 RunLoop 启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode,如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

    CFRunLoopModeRef主要是用来指定事件在运行循环中的优先级

    • NSDefaultRunLoopMode :默认,空闲状态的Mode
    • UITrackingRunLoopMode:scrollView滑动时会切换到该Mode
    • UIInitializationRunLoopMode :RunLoop启动时,会切换到该Mode
    • GSEventReceiveRunLoopMode: Graphic相关事件的mode,通常用不到。
    • NSRunLoopCommonModes :Mode集合

    主线程里默认UITrackingRunLoopMode, kCFRunLoopDefaultMode 被标记了Common,子线程中默认只有 kCFRunLoopDefaultMode 被标记。
    CFRunLoop对外暴露的管理 Mode 接口只有下面2个:

    CFRunLoopAddCommonMode(CFRunLoopRef runloop, CFStringRef modeName);
    CFRunLoopRunInMode(CFStringRef modeName, ...);
    
    • 1
    • 2

    Mode 暴露的管理 mode item 的接口有下面几个:

    CFRunLoopAddSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    CFRunLoopAddObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
    CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
    CFRunLoopRemoveSource(CFRunLoopRef rl, CFRunLoopSourceRef source, CFStringRef modeName);
    CFRunLoopRemoveObserver(CFRunLoopRef rl, CFRunLoopObserverRef observer, CFStringRef modeName);
    CFRunLoopRemoveTimer(CFRunLoopRef rl, CFRunLoopTimerRef timer, CFStringRef mode);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain()CFRunLoopGetCurrent()

    CFRunLoopSourceRef 是事件源(输入源)

    即事件产生的地方, Source有两个版本:Source0 和 Source1。

    按照函数调用栈的分类
    1、Source0:非基于Port的 ,用于用户主动触发事件
    2、Source1:基于Port的,通过内核和其它线程相互发送消息(触摸/锁屏/摇晃等)

    struct __CFRunLoopSource {
        CFRuntimeBase _base; 
        uint32_t _bits;  //用于标记Signaled状态,source0只有在被标记为Signaled状态,才会被触发
        pthread_mutex_t _lock;
        CFIndex _order;         /* immutable */
        CFMutableBagRef _runLoops;
        union {
            CFRunLoopSourceContext version0;     // source0
            CFRunLoopSourceContext1 version1;    // source1
        } _context;
    };
    
    typedef struct {
        CFIndex version;
        void *  info;
        void    (*schedule)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
        void    (*cancel)(void *info, CFRunLoopRef rl, CFRunLoopMode mode);
        void    (*perform)(void *info);
    } CFRunLoopSourceContext;
    
    typedef struct {
        CFIndex version;
        void *  info;
        mach_port_t (*getPort)(void *info);
        void *  (*perform)(void *msg, CFIndex size, CFAllocatorRef allocator, void *info);
     
    } CFRunLoopSourceContext1;
     
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    CFRunLoopTimerRef 是基于时间的触发器

    它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

    struct __CFRunLoopTimer {
        CFRuntimeBase _base;
        uint16_t _bits; //标记fire状态
        pthread_mutex_t _lock;
        CFRunLoopRef _runLoop; //添加该timer的runloop
        CFMutableSetRef _rlModes; //存放所有 包含该timer的 mode的 modeName,意味着一个timer可能会在多个mode中存在
        CFAbsoluteTime _nextFireDate; // 下一次触发时间
        CFTimeInterval _interval;    //执行的时间间隔
        CFTimeInterval _tolerance;   //时间偏差
        uint64_t _fireTSR;      // 触发时间
        CFIndex _order;         /* immutable */
        CFRunLoopTimerCallBack _callout;    // 回调
        CFRunLoopTimerContext _context;  // 回调内容
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    CFRunLoopObserverRef 是观察者

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

    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
    };
    
    struct __CFRunLoopObserver {
        CFRuntimeBase _base;
        pthread_mutex_t _lock;  // 线程锁
        CFRunLoopRef _runLoop; // 所在的runloop
        CFIndex _rlCount; // Observer监控的runloop数量 逻辑主要在__CFRunLoopObserverSchedule和__CFRunLoopObserverCancel
        CFOptionFlags _activities;      // RunLoop的几个状态 - immutable
        CFIndex _order;         /* immutable */
        CFRunLoopObserverCallBack _callout; // 回调
        CFRunLoopObserverContext _context;  // 回调的参数
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    监听runloop状态:

    	CFRunLoopObserverRef observerRef = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, -12222222, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
            switch (activity) {
                case kCFRunLoopEntry:
                    NSLog(@"kCFRunLoopEntry");
                    break;
                case kCFRunLoopBeforeTimers:
                    NSLog(@"kCFRunLoopBeforeTimers");
                    break;
                case kCFRunLoopBeforeSources:
                    NSLog(@"kCFRunLoopBeforeSources");
                    break;
                case kCFRunLoopBeforeWaiting:
                    NSLog(@"kCFRunLoopBeforeWaiting");
                    break;
                case kCFRunLoopAfterWaiting:
                    NSLog(@"kCFRunLoopAfterWaiting");
                    break;
                case kCFRunLoopExit:
                    NSLog(@"kCFRunLoopExit");
                    break;
                case kCFRunLoopAllActivities:
                    NSLog(@"kCFRunLoopAllActivities");
                    break;
                    
                default:
                    NSLog(@"default");
                    break;
            }
        });
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observerRef, kCFRunLoopDefaultMode);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30

    RunLoop运行逻辑:图文释义

    /// 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;
                }
    
                /// 6.通知 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);
    
                /// 9.收到消息,处理消息。
                handle_msg:
    
                /// 10.1 如果一个 Timer 到时间了,触发这个Timer的回调。
                if (msg_is_timer) {
                    __CFRunLoopDoTimers(runloop, currentMode, mach_absolute_time())
                } 
    
                /// 10.2 如果有dispatch到main_queue的block,执行block。
                else if (msg_is_dispatch) {
                    __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg);
                } 
    
                /// 10.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);
        }
    
        /// 11. 通知 Observers: RunLoop 即将退出。
        __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101

    自己对RunLoop的理解

    首先我们知道Runtime是OC的底层,通过消息机制在运行时动态的确定调用方法和事件处理,那Runloop充当什么角色呢?

    程序启动,main函数会在主线程启动一个mainLoop,上图是一个按钮的点击事件,自下而上可以看到Runloop的大概脉络,接收到了Source0的事件源,处理block时,通过 Runtime 找到 isaclassIMP 响应方法(这块涉及 Runtime事件传递 的知识),然后runloop进入休眠等待唤醒,mainLoop不会退出,子线程的runloop当没有 Source / Timer 时就会退出,结束线程。

    网上很多分析和讲解Runloop底层的,唯有ibireme大神讲解的入骨三分,大家可以拜读一下。

  • 相关阅读:
    Kotlin协程-select基础
    CodeForces..学习读书吧.[简单].[条件判断].[找最小值]
    通过京东商品ID获取京东优惠券信息,京东优惠券信息接口,京东优惠券API接口,接口说明接入方案
    STC 51单片机39——汇编语言 按钮流水灯
    Spring循环依赖
    MySQL面试题
    练习题60:接口练习2:真的鸭子会游泳 木头鸭子不会游泳 橡皮鸭子会游泳
    java第二讲:运算符与流程控制
    Nlog 集成到WebApi
    每个前端都应该掌握的7个代码优化的小技巧
  • 原文地址:https://blog.csdn.net/wujakf/article/details/125930536