美文网首页
iOS RunLoop(1)-底层解析

iOS RunLoop(1)-底层解析

作者: switer_iOS | 来源:发表于2021-06-29 09:22 被阅读0次

    1. RunLoop定义

    RunLoop:运行循环,在程序运行过程中循环做一些事情。所涉及的范畴包括:
    ① 定时器(Timer)、PerformSelector;
    ② GCD Async Main Queue;
    ③ 事件响应、手势识别、界面刷新;
    ④ 网络请求;
    ⑤ AutoreleasePool。

    image

    通常情况下如果我们创建一个Command Line Tool项目,如下:

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

    程序在执行到Line13之后,马上会执行Line15,然后程序退出,因为此段代码中没有runloop.
    但是在我们的iOS项目中并不是和Command Line Tool一样写main函数的.

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

    我们可以通过runloop的源码得知runloop的原理,所以我们模拟可以写代码模拟

    int main(int argc, char * argv[]) {
        @autoreleasepool {
            int retVal = 0;
            do {
                // 睡眠中等待消息
                int message = sleep_and_wait();
                // 处理消息
                retVal = process_message(message);
            } while (0 == retVal);
    
            return 0;
        }
    }
    
    

    有了RunLoop,程序并不会马上退出,而是保持运行状态。RunLoop可以处理App中的各种事件(比如触摸事件、定时器事件等);RunLoop可以在该做事的时候做事,该休息的时候休息,从而节约CPU资源,提高程序性能。
    iOS中提供两套API来访问和使用RunLoop,分别为Foundation框架下面的NSRunLoop和Core Foundation下面的CFRunLoopRef。
    NSRunLoop和CFRunLoopRef都是RunLoop对象,其中NSRunLoop是基于CFRunLoopRef的一层OC包装(参考图NSRunLoop是基于CFRunLoopRef的一层OC包装.png),CFRunLoopRef是开源的,开源地址

    image

    上图中,touch事件在主线程中触发,主线程所对应的RunLoop为mainRunLoop,通过OC与C语言的API访问RunLoop地址,可见两者指针不同,但是NSLog(@"%@",[NSRunLoop currentRunLoop]);输出的RunLoop详细信息发现NSRunLoop本质也是一个CFRunLoop,并且其地址与CFRunLoopGetMain()相同,进而可以说明NSRunLoop是基于CFRunLoopRef的一层OC包装。

    2. RunLoop与线程的关系

    平时都是通过:获取当前线程CFRunLoopGetCurrent();,获取主线程CFRunLoopGetMain();.下面我们就来分析一下这两个函数都做了什么,我们可以通过CFRunLoop源码来看一下.

    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;
    }
    
    CFRunLoopRef CFRunLoopGetCurrent(void) {
        CHECK_FOR_FORK();
        CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
        if (rl) return rl;
        return _CFRunLoopGet0(pthread_self());
    }
    
    

    在调用CFRunLoopGetCurrent(),CFRunLoopGetMain()函数时,函数内部都会调用_CFRunLoopGet0(pthread_t t)函数,并传入不同的线程作为参数。可见调用CFRunLoopGetMain()时,传入的是主线程,调用CFRunLoopGetCurrent()时传入的是当前的线程(子线程或主线程)。接下来继续看看_CFRunLoopGet0(pthread_t t)函数:

    // should only be called by Foundation
    // t==0 is a synonym for "main thread" that always works
    CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) {
        if (pthread_equal(t, kNilPthreadT)) {
        t = pthread_main_thread_np();
        }
        __CFLock(&loopsLock);
        if (!__CFRunLoops) {
            __CFUnlock(&loopsLock);
        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);
        }
        CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        __CFUnlock(&loopsLock);
        if (!loop) {
        CFRunLoopRef newLoop = __CFRunLoopCreate(t);
            __CFLock(&loopsLock);
        loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
        if (!loop) {
            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())) {
            _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL);
            if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) {
                _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop);
            }
        }
        return loop;
    }
    
    

    透过_CFRunLoopGet0(pthread_t t)源码,如果__CFRunLoops不存在,系统就会利用当前的主线程"pthread_main_thread_np()"创建一个mainLoop,也就是“mainRunLoop”,然后将创建RunLoop的线程作为key,RunLoop作为value添加到一个全局的字典CFDictionarySetValue中。最后获取"pthread_t"线程对应的loop并返回loop。如果"pthread_t"不是主线程,用同样的方法创建一个新的RunLoop,即"newLoop",同样将"newLoop"存放到字典中然后返回newLoop。所以关于RunLoop与线程的关系可以总结如下:

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

    3. RunLoop对象结构

    获取RunLoop对象

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

    我们可以用NSLog(@"%@",[NSRunLoop currentRunLoop]);方法打印Runloop对象,打印出RunLoop大概包含current mode、common modes、common mode items、modes这些信息。实际上从开源的文档里,RunLoop包含的成员远不止上面这些,具体包含内容如下图:

    image

    其中比较重要的是"_modes"这个成员,它是一个集合类型,里面存放了多个CFRunLoopMode类型的mode成员,一个RunLoop可以有多个mode,这些mode都包含在"_modes"里。CFRunLoopMode结构体如下:

    image

    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会立马退出

    4. CFRunLoopModeRef的常用的两种model

    kCFRunLoopDefaultMode(NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行;
    UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响;
    其实还有一种特殊的模式kCFRunLoopCommonModes 的模式NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式 NSRunLoopCommonModes并不是一个真的模式,它只是一个标记;

    每一种mode里面都有source0、source1、observers、timers,他们主要负责的内容如下:

    • Source0
      触摸事件处理
      performSelector:onThread:
    • Source1
      基于Port的线程间通信
      系统事件捕捉
    • Timers
      NSTimer
      performSelector:withobiect:afterDelay;
    • Observers
      用于监听Runloop的状态
      UI刷新( BeforeWaiting)
      Autorelease pool ( BeforeWaiting )

    两种方式监听RunLoop状态:

    方式①:
    - (void)viewDidLoad {
    
        [super viewDidLoad];
        // 创建Observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
    
            CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
            switch (activity) {
                case kCFRunLoopEntry: {
                    NSLog(@"kCFRunLoopEntry - %@", mode);
                    CFRelease(mode);
                    break;
                }
                case kCFRunLoopBeforeTimers:{
                    NSLog(@"kCFRunLoopBeforeTimers - %@", mode);
                      CFRelease(mode);
                    break;
                }
                case kCFRunLoopBeforeSources:{
                    NSLog(@"kCFRunLoopBeforeSources - %@", mode);
                      CFRelease(mode);
                    break;
                }
                case kCFRunLoopBeforeWaiting:{
                    NSLog(@"kCFRunLoopBeforeWaiting - %@", mode);
                      CFRelease(mode);
                    break;
                }
                case kCFRunLoopAfterWaiting:{
                    NSLog(@"kCFRunLoopAfterWaiting - %@", mode);
                      CFRelease(mode);
                    break;
                }
                case kCFRunLoopExit: {
                    NSLog(@"kCFRunLoopExit - %@", mode);
                    CFRelease(mode);
                    break;
                }
    
                default:
                    break;
            }
        });
        // 添加Observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放
        CFRelease(observer);
    }
    
    方式②:
    void observeRunLoopActicities(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
    {
        CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
        switch (activity) {
            case kCFRunLoopEntry:
                 NSLog(@"kCFRunLoopEntry - %@", mode);
                break;
            case kCFRunLoopBeforeTimers:
                 NSLog(@"kCFRunLoopBeforeTimers - %@", mode);
                break;
            case kCFRunLoopBeforeSources:
                 NSLog(@"kCFRunLoopBeforeSources - %@", mode);
                break;
            case kCFRunLoopBeforeWaiting:
                 NSLog(@"kCFRunLoopBeforeWaiting - %@", mode);
                break;
            case kCFRunLoopAfterWaiting:
                 NSLog(@"kCFRunLoopAfterWaiting - %@", mode);
                break;
            case kCFRunLoopExit:
                 NSLog(@"kCFRunLoopExit - %@", mode);
                break;
            default:
                break;
        }
         CFRelease(mode);
    }
    
    - (void)viewDidLoad {
        [super viewDidLoad];
    
        // kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
    
        // 创建Observer
        CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
        // 添加Observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        // 释放
        CFRelease(observer);
    }
    
    

    CFRunLoopObserverRef的几种状态

    /* 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 = 0x0FFFFFFU
    }
    

    相关文章

      网友评论

          本文标题:iOS RunLoop(1)-底层解析

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