RunLoop

作者: iVikings | 来源:发表于2020-05-09 10:35 被阅读0次

    RunLoop 顾名思义:运行循环,在程序运行过程中循环做一些事情,即用来处理事件的循环

    RunLoop 应用范畴

    • 定时器(Timer)、PerformSelector
    • GCD Async Main Queue
    • 事件响应、手势识别、界面刷新
    • 网络请求(如:AFNetworking 线程保活)
    • AutoreleasePool

    RunLoop 基本作用

    • 保持程序持续运行:程序一启动就会创建一个主线程,一个主线程会有与之对应的RunLoopRunLoop保证主线程不会被销毁,也就保证了程序的持续运行
    • 处理App中的各种事件(比如:触摸事件、定时器事件、Selector事件等)
    • 节省 CPU 资源,提高程序性能:程序运行起来时,当什么操作都没有做的时候,RunLoop就会通知 CPU,现在没有事情做,我要去休息,这时CPU 就会将其资源释放出来去做其他的事情,当有事情做的时候RunLoop就会被唤醒去做事情。(没有消息就让线程休眠,有消息就唤醒线程)

    RunLoop 对象

    iOS 中有2套API来访问和使用 RunLoop:

    • Foundation:NSRunLoop
    • Core Foundation:CFRunLoopRef

    NSRunLoopCFRunLoopRef 都代表着 RunLoop对象

    RunLoop 与线程

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

    获取 RunLoop 对象

    • Foundation
    [NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
    [NSRunLoop mainRunLoop];    // 获得主线程的RunLoop对象
    
    • Core Foundation
    CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
    CFRunLoopGetMain();    // 获得主线程的RunLoop对象
    

    RunLoop 的构成

    RunLoop
    • Core Foundation 中关于 RunLoop 的5个类

      CFRunLoopRef // runloop对象
        CFRunLoopModeRef  // 运行模式
          CFRunLoopSourceRef
          CFRunLoopTimerRef
          CFRunLoopObserverRef
      

    结构体如下:

    typedef struct __CFRunLoop * CFRunLoopRef;
    typedef struct __CFRunLoopSource * CFRunLoopSourceRef;
    typedef struct __CFRunLoopObserver * CFRunLoopObserverRef;
    
    struct __CFRunLoop {
        pthread_t _pthread; // 线程 
        CFMutableSetRef _commonModes; // commonModes下的两个mode(kCFRunloopDefaultMode和UITrackingMode)
        CFMutableSetRef _commonModeItems; // 在commonModes状态下运行的对象(例如Timer)
        CFRunLoopModeRef _currentMode; // 运行的所有模式(CFRunloopModeRef类)
        CFMutableSetRef _modes; // 在当前 loop 下运行的mode
        ...
    };
    
    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    struct __CFRunLoopMode {
        CFStringRef _name; // Mode Name, 例如 @"kCFRunLoopDefaultMode"
        CFMutableSetRef _sources0;
        CFMutableSetRef _sources1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
        ...
    };
    

    CFRunLoopModeRef

    • CFRunLoopModeRef 代表 RunLoop 的运行模式
    • 一个RunLoop 包含 若干个Mode每个Mode 又包含若干个 Source0/Source1/Timer/Observer
    • RunLoop 启动时只能选择其中一个Mode,作为 currentMode
    • 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入

      不同组的 Source0/Source1/Timer/Observer 能分隔开来,互不影响

    • 如果 Mode 里没有任何 Source0/Source1/Timer/ObserverRunLoop 会立马退出
    目前已知的 5 种 Mode:
    • kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
    • UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
    • UIInitializationRunLoopMode:在刚启动 App 时进入的第一个 Mode,启动完成后就不再使用
    • GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
    • kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode,包含 kCFRunLoopDefaultModeUITrackingRunLoopMode

    CommonModes

    一个 Mode 可以将自己标记为Common属性(通过将其 ModeName 添加到 RunLoopcommonModes 中)。每当 RunLoop 的内容发生变化时,RunLoop 都会自动将 _commonModeItems 里的 Source/Observer/Timer 同步到具有 Common 标记的所有 Mode 里。

    _sources0 和 _sources1

    • Sources0: 触摸事件处理,performSelector: onThread:,非基于Port的
    • Sources1: 基于Port的线程间通信,系统事件捕捉

    _timers

    定时执行的定时器,底层基于使用 mk_timer 实现,受 RunLoop 的 Mode 影响(GCD的定时器不受 RunLoop 的Mode 影响);当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。如果线程阻塞或者不在这个Mode下,触发点将不会执行,一直等到下一个周期时间点触发。

    timer 和 sources1(也就是基于 port 的 sources)可以反复使用,比如 timer 设置为 repeat,port 可以持续接收消息,而 sources0 在一次触发后就会被 runloop 移除。

    _observers

    • 用于监听 RunLoop 的状态
    • UI刷新(BeforeWaiting)
    • AutoreleasePool(BeforeWaiting)
    监听方法
    // 创建Observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        switch (activity) {
            case kCFRunLoopEntry: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopEntry - %@", mode);
                CFRelease(mode);
                break;
            }
            case kCFRunLoopExit: {
                CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
                NSLog(@"kCFRunLoopExit - %@", mode);
                CFRelease(mode);
                break;
            }
            default:
                break;
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
    CFRelease(observer);
    
    监听状态
    /* Run Loop Observer Activities */
    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
        kCFRunLoopAllActivities = 0x0FFFFFFFU  // 包含上面所有状态
    };
    

    RunLoop 运行逻辑

    RunLoop 运行逻辑
    查看源码
    void CFRunLoopRun(void) {   /* DOES CALLOUT */
        int32_t result;
        do {
            result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
            CHECK_FOR_FORK();
        } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
    }
    
    SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled);
    
    static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode);
    
    

    RunLoop 休眠的实现原理

    RunLoop 休眠的实现原理

    相关文章

      网友评论

          本文标题:RunLoop

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