美文网首页
iOS-RunLoop01-基本认识

iOS-RunLoop01-基本认识

作者: IBigLiang | 来源:发表于2019-08-29 17:46 被阅读0次

    RunLoop,顾名思义就是一种iOS框架中的运行循环,而程序在运行中,这个循环一直在为程序做一些事情。
    首先大家都知道,我们创建一个项目,在main.m文件的main函数中包含以下内容:

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

    其实,这个return的内容就是一个RunLoop,其主要目的就是要程序一直进行,不被退出,简单点说就是一个死循环,二在这个循环中,程序可以做自己想做的事,一旦退出循环,程序就结束应用。
    除了上述所说,RunLoop可以保持程序的持续运行,它还可以帮我们处理APP运行中的各种事件,比如触摸事件啊,定时器事件等等。而且它还有一个很大的特点,就是在没有事件需要处理的时候,它就会进入休眠,等待下一次被唤醒,在休眠过程中,基本不消耗CPU资源。
    在iOS中有Foundation框架中的NSRunLoop和CoreFoundation框架中的CFRunLoop两套关于RunLoop的API可供我们使用,这里我们先简单的来运用下吧:

    //当获取到一个RunLoop时,首先它自动会去帮我们创建一个fRunLoop
        // OC
        NSRunLoop *runLoop =  [NSRunLoop currentRunLoop]; // 创建一个RunLoop
        [runLoop run]; // 运行RunLoop
        
        [NSRunLoop mainRunLoop]; // 获取主线程RunLoop
    
        // C
        CFRunLoopGetCurrent(); // 创建一个RunLoop
        CFRunLoopRun(); // 运行RunLoop
        
        CFRunLoopGetMain(); // 获取主线程RunLoop
    

    以上是在两个框架下,最简单的RunLoop运用。在上面代码中,我们可以看到,我们可以直接获取一个RunLoop,比方说mainRunLoop,这个方法获取到的是主线程的RunLoop,其实没一个RunLoop都会对应这一条线程,mainRunLoop就对应着我们的主线程,而子线程中也有着与自己对应的RunLoop,就比如如下的代码中:

    - (void)viewDidLoad {
        NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(threadFun) object:nil];
        [thread start];   
    }
    
    - (void)threadFun {
        NSRunLoop *runLoop =  [NSRunLoop currentRunLoop]; // 创建一个RunLoop
        NSLog(@"%p, %p", runLoop, [NSRunLoop mainRunLoop]);
    }
    

    打印出来的结果是:

    2019-08-29 15:35:10.753941+0800 001-RunLoop使用[68546:25248318] 0x600000244ba0, 0x600000249080
    

    很明显,子线程中创建的RunLoop和主线程的RunLoop不是系统一个,所以每一个线程都对应着一个RunLoop,而且,需要知道的是,RunLoop在线程结束的时候,就会销毁,除非用特定的方法不让线程结束,这个我们后面再讨论。
    接下来,我们再来看看和runloop相关的类,这里我用CoreFoundation框架来展示,有以下这几个类:

        CFRunLoopMode // RunLoop模式
        CFRunLoopRef //RunLoop类
        CFRunLoopSourceRef // source(各种事件处理)
        CFRunLoopObserverRef //观察者(用于监听RunLoop状态)
        CFRunLoopTimerRef //定时器
    

    这里我们看不到底层的源码,所以按照之前的方法,我们去下载源码来看对应的知识点,源码地址:https://opensource.apple.com/tarballs/CF/,下载完之后,打开找到CFRunLoop.c文件,我们先来看CFRunLoopRef这个类型,它对应的是struct __CFRunLoop 这个结构体,这里我们先把结构体内主要的部分抽出来:

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

    从抽出来的代码中,我们也可以了解到,每一个RunLoop都对应着一个线程,然后我们再主要来看看CFRunLoopModeRef这个类型,可以找到:

    typedef struct __CFRunLoopMode *CFRunLoopModeRef;
    

    同样也是一个结构体,抽取之后如下:

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

    到这里,我们基本上就能看清一个RunLoop的内部结构了,用一张图来总结就是如下:


    image.png

    所以,总结得到,RunLoop中包含若干个_commonModes,但是每次只能选择其中一个_currentMode,而每一个_currentMode中又包含若干个Source0/Source1/Timer/Observer
    ,而且不同组的Source0/Source1/Timer/Observer都是分隔开的,互不影响,当然当mode中没有Source0/Source1/Timer/Observer,这时候RunLoop会退出。
    然后我们来看看常见的几种model,一种是kCFRunLoopDefaultMode(NSDefaultRunLoopMode),一种是UITrackingRunLoopMode。
    NSDefaultRunLoopMode是默认模式,通常主线程就是在这个模式下。UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响。
    然后我们再看CFRunLoopObserverRef这个监听着RunLoop状态的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
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    

    这里,我们可以利用在主线程的RunLoop中添加Observer来检测RunLoop在不同模式下的切换,代码如下:

    - (void)viewDidLoad {
        
        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(@"kCFRunLoopEntry - %@", mode);
                    CFRelease(mode);
                    break;
                }
                    
                default:
                    break;
            }
            
        });
        CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
        CFRelease(observer);
    }
    

    在项目中的Main.storyboard中根视图上添加一个textView,然后在程序启动之后进行滚动,你会发现控制台的打印:

    2019-08-29 17:41:10.888940+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - kCFRunLoopDefaultMode
    2019-08-29 17:41:10.889159+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - UITrackingRunLoopMode
    2019-08-29 17:41:12.873182+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - UITrackingRunLoopMode
    2019-08-29 17:41:12.873288+0800 001-RunLoop使用[70732:25388149] kCFRunLoopEntry - kCFRunLoopDefaultMode
    

    很明显主线程的RunLoop进行的切换。
    由于时间关系,今天暂时分享到这个,之后我会继续分享RunLoop的运行逻辑,以及RunLoop在实际开发中的应用。

    相关文章

      网友评论

          本文标题:iOS-RunLoop01-基本认识

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