美文网首页
【iOS重学】关于RunLoop的简单介绍

【iOS重学】关于RunLoop的简单介绍

作者: 重庆妹子在霾都 | 来源:发表于2023-02-12 16:11 被阅读0次

    写在前面

    本文主要是记录关于RunLoop的一些简单介绍。

    RunLoop

    基本认识

    RunLoop:翻译过来叫运行时循环,指的是在程序运行过程中循环的做一些事情。

    主要应用在:

    • 定时器(Timer)、PerformSelector
    • GCD
    • 事件响应、手势识别、界面刷新
    • 网络请求
    • AutoReleasePool
      1.png
      上面是我们一个iOS程序的入口main函数,在UIApplicationMain函数中会去创建主线程的RunLoop对象,它用来保证程序不退出从而保证程序的持续执行。
      我们可以把RunLoop运行时循环理解成一个do - while循环,伪代码如下:
    int main(int argc, char * argv[]) {
      NSString * appDelegateClassName;
      @autoreleasepool {
        int retVal = 0;
        do {
          // 1.在休眠中等待消息
          
          // 2.如果有消息 处理消息
          
        }while(retVal = 0);
        return 0;
      }
    }
    

    RunLoop的基本作用:

    • 保证程序的持续运行
    • 处理App中的各种事件(比如触摸事件、定时器等)
    • 节省CPU资源,提高程序的性能:在有消息的时候处理消息 没有消息的时候休眠。

    RunLoop对象

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

    其中NSRunLoop是基于CFRunLoopRef的一层OC封装。

    RunLoop与线程的关系

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

    获取RunLoop对象

    Foundation框架:

    [NSRunLoop currentRunLoop]; // 获取当前线程的RunLoop对象
    [NSRunLoop mainRunLoop]; // 获取主线程的RunLoop对象
    

    Core Foundation框架:

    CFRunLoopGetCurrent(); // 获取当前线程的RunLoop对象
    CFRunLoopGetMain();// 获取主线程的RunLoop对象
    

    RunLoop相关的类

    Core Foundation框架中关于RunLoop的五个类:

    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef

    RunLoop对象结构如下:

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

    RunLoopMode结构如下:

    struct __CFRunLoopMode {
    CFStringRef _name;
    CFMutableSetRef _sources0;
    CFMutableSetRef _sources1;
    CFMutableArrayRef _observers;
    };
    

    各个类之间的关系如下图:


    2.png

    RunLoop的运行模式

    CFRunLoopModeRef表示是RunLoop的运行模式,一个RunLoop可以有若干个Mode,每个Mode里面又包含Source0、Source1、Timer、Observer。
    RunLoop在启动时只能选择其中的一个Mode作为CurrentMode。
    如果需要切换Mode需要退出当前RunLoop重新选择一个Mode进入。
    不同模式下的Source0/Source1/Timer/Observer能分隔开来,互不影响。
    如果一个Mode中没有任何Source0/Source1/Timer/Observer,这个RunLoop会立马退出。

    常见的Mode有两种:
    NSDefaultRunLoopMode(kCFRunLoopDefaultMode):App的默认Mode,通常主线程在这个Mode下运行。
    UITrackingRunLoopMode:界面跟踪Mode,ScrollView的滑动,保证界面滑动时不受其他的影响。

    RunLoop的几种状态

    /* Run Loop Observer Activities */
    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0), // 即将进入RunLoop
        kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
        kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
        kCFRunLoopBeforeWaiting = (1UL << 5), // 即将进入休眠
        kCFRunLoopAfterWaiting = (1UL << 6), // 刚从休眠中被唤醒
        kCFRunLoopExit = (1UL << 7), // 即将退出RunLoop
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    

    如下可以监听RunLoop的所有状态:

    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(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;
            default:
                break;
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
    CFRelease(observer);
    

    RunLoop的运行逻辑

    1、 Source0

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

    2、Source1

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

    3、Timers

    • NSTimer
    • performSelector:withObject:afterDelay:

    4、Observers

    • 用于监听RunLoop的状态
    • UI刷新(BeforeWaiting)
    • AutoReleasePool(BeforeWaiting)

    RunLoop的运行逻辑如下:


    3.png

    RunLoop在实际开发中的应用

    • 控制线程的生命周期(线程保活)
    • 解决NSTimer在滑动时停止工作的问题
    • 监控应用卡顿
    • 性能优化

    RunLoop的源码查看

    4.png
    从上面看到:RunLoop的源码入口在CFRunLoopRunSpecific

    注意
    1、使用Foundation框架打印出来的主线程RunLoop和Core Foundation框架打印出来的主线程RunLoop地址值不一样,原因在于Foundation框架的RunLoop是对Core Foundation框架RunLoop的一层封装。
    2、系统事件是通过Source1来捕捉,之后分发到Source0去处理的。
    3、RunLoop在休眠之前会去释放自动释放池和刷新UI等。
    4、线程阻塞和RunLoop休眠不一样:线程阻塞还是在执行代码 当前线程根本没有真的休眠 RunLoop休眠真的是休眠 没有执行代码 CPU不会为此分配资源 就会省电。

    写在最后

    关于RunLoop的一些基本介绍、各种模式以及它整个完整的运行逻辑就介绍到这里了,如有错误请多多指教,最后欢迎去我的个人技术博客逛逛。

    相关文章

      网友评论

          本文标题:【iOS重学】关于RunLoop的简单介绍

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