RunLoop

作者: MeliodasLy | 来源:发表于2018-06-09 18:16 被阅读17次

    RunLoop

    顾名思义

    • 运行循环
    • 在程序运行过程中循环做一些事情
    • 一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出,通常的代码逻辑是这样的:
    function loop() {
        initialize();
        do {
            var message = get_next_message();
            process_message(message);
        } while (message != quit);
    }
    

    这种模型通常被称作Event Loop
    实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立即被唤醒。

    RunLoop实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面的Event Loop的逻辑。线程执行了这个函数后,就会一直处于正常函数内部“接受消息->等待->处理”的循环中,直到这个循环结束(比如传入quit的消息),函数返回。


    应用范畴

    • 定时器(Timer)、performSelector
    • GCD Async Main Queue
    • 事件响应、手势识别、界面刷新
    • 网络请求
    • AutoreleasePool

    RunLoop的基本作用

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

    RunLoop对象

    • iOS中有2套API来访问和使用RunLoop
      • Foundation:NSRunLoop
      • Core Foundation:CFRunLoopRef
    • NSRunLoop和CFRunLoopRef都代表着RunLoop对象
      • NSRunLoop是基于CFRunLoopRef的一层OC包装(所以runloop的地址会不一样
        • 提供了面向对象的API,但是这些API不是线程安全的
      • CFRunLoopRef是开源的
        • 提供了纯C函数的API,所有这些API都是线程安全的
        // OC获取当前的RunLoop
        NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
        // OC获取主线程RunLoop
        NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
        // C语言获取当前的RunLoop
        CFRunLoopRef currentRunloop2 = CFRunLoopGetCurrent();
        // C语言获取主线程的RunLoop
        CFRunLoopRef mainRunloop2 = CFRunLoopGetMain();
    

    RunLoop与线程

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

    RunLoop相关的类

    • Core Foundation中关于RunLoop的5个类
      • CFRunLoopRef
      • CFRunLoopModeRef
      • CFRunLoopSourceRef 事件产生的地方
      • CFRunLoopTimerRef
      • CFRunLoopObserverRef

    CFRunLoopRef

    typedef struct __CFRunLoop * CFRunLoopRef;
    struct __CFRunLoop {
        pthread_t _pthread; // RunLoop所对应的线程
        CFMutableSetRef _commonModes;
        CFMutableSetRef _commonModeItems;
        CFRunLoopModeRef _currentMode; // _modes这个集合里面只有一个模式被称为当前模式_currentMode
        CFMutableSetRef _modes; // 这个集合里面装了很多CFRunLoopModeRef类型的对象
    };  
    

    CFRunLoopModeRef

    • CFRunLoopModeRef代表RunLoop的运行模式
    • 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
    • RunLoop启动时只能选择其中一个Mode,作为currentMode
    • 如果需要切换Mode,只能退出当前Loop,在重新选择一个Mode进入
      • 目的:不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响
    • 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
    常见的2种Mode
    • kCFRunLoopDefaultMode(等价于NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
    • UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
    • 另外还有一种模式:kCFRunLoopCommonModes 默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode两种模式
    typedef struct __CFRunLoopMode * CFRunLoopModeRef;
    struct __CFRunLoopMode {
        CFStringRef _name; // mode的名称
        CFMutableSetRef _sources0;
        CFMutableSetRef _sources1; // source里面装了很多CFRunLoopSourceRef类型的对象
        CFMutableArrayRef _observers;// 里面装了很多CFRunLoopObserverRef类型的对象
        CFMutableArrayRef _timers; // 里面装了很多CFRunLoopTimerRef类型的对象
    };
    

    CFRunLoopObserverRef

    /* RunLoop 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
    };
    
    // 监听事件的状态变化(只有c语言接口)
    // 方法一
    void observeRunLoopActivities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info){
        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;
        }
    }
    
        // 创建observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActivities, NULL);
        // 添加observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放observer
        CFRelease(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;
            }
        });
        // 添加observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放observer
        CFRelease(observer);
    

    根据源码分析,得到如下关系图

    runloop

    RunLoop的运行逻辑

    • Source0
      • 触摸事件处理
      • performSelector: onThread:(提供一个线程,就可以让对应方法去该线程去执行)
    • Source1
      • 基于Port的线程间通信
      • 系统事件捕捉
    • Timers
      • NSTimer
      • performSelector: withObject: afterDelay:
    • Observers
      • 用于监听RunLoop的状态
      • UI刷新(BeforeWaiting)
      • AutoReleasePool(BeforeWaiting)

    如下图所示


    runloop流程

    RunLoop休眠的实现原理

    休眠的实现原理

    实质:从用户态切换到内核态,去等待消息,没有消息就让线程休眠,不在占用cpu,有消息就唤醒线程。

    RunLoop在实际开发中的应用

    • 控制线程生命周期(线程保活,比如AFNetWorking都是在子线程中等待的,所以是一直保活)

    • 解决NSTimer在滑动时停止工作的问题

    static int count = 0;
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            NSLog(@"%d",++count);
        }];
    // NSDefaultRunLoopMode/UITrackingRunLoopMode才是真正存在的模式    
    // NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
    // timer能在_commonModes数组中存放的模式下工作
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    //    [NSTimer scheduledTimerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
    //        NSLog(@"%d",++count);
    //    }];
    
    • 监控应用卡顿
    • 性能优化

    相关文章

      网友评论

        本文标题:RunLoop

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