美文网首页
RunLoop详解

RunLoop详解

作者: 爱看书de图图 | 来源:发表于2018-09-04 18:21 被阅读57次

    RunLoop介绍

      顾名思义,运行循环,是程序在运行中做的一些事情,RunLoop是iOS运行过程中循环做一些事情,RunLoop运用非常广泛,比如:定时器(timer)、PerformSelector、GCD Async Main Queue、事件响应、手势识别、界面刷新、网络请求、AutoreleasePool等等等等。所以说,没有RunLoop,iOS程序所做的很多事情都无法完成,下面我们就详细介绍一下RunLoop底层是如何实现,以及RunLoop在iOS开发中的实际运用。
      如果没有RunLoop,代码运行完后,程序直接退出,我举个例子,大家可以用xcode新建一个命令行项目,命令行项目默认没有RunLoop。

    #import <Foundation/Foundation.h>
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSLog(@"Hello, World!");
        }
        return 0;
    }
    
      这段代码打印结果 运行结果.png xcode.png

      大家可以看到,程序显示已经运行结束,并且Xcode上方显示Finish running。因为命令行程序一旦执行完毕,程序会走到return 0然后退出程序。但是如果有了RunLoop,RunLoop会帮我们做一些事情,让程序不会return,这样程序会在休眠中一直等待消息唤醒,比如处理点击事件,定时器等。而且,RunLoop还会降低CPU的消耗,让程序在不必要的时候处于休眠状态以节省性能。RunLoop的概念介绍完毕,大家对RunLoop有了一个粗浅的认识,接下来我们详细介绍一下RunLoop对象,以及RunLoop对象底层的原理。

    RunLoop对象

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

    • Foundation:NSRunLoop (OC)
    • Core Foundation:CFRunLoopRef(C语言)

    NSRunLoop是对CFRunLoopRef的一层包装,CFRunLoopRef是开源的,RunLoop对象也有对应的两种创建方式

        NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
        CFRunLoopRef *runLoop CFRunLoopGetCurrent();
    

    RunLoop与线程的关系

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

    RunLoop相关的类以及结构

    Core Foundation中关于RunLoop的5个类

    • CFRunLoopRef
    • CFRunLoopModeRef
    • CFRunLoopSourceRef
    • CFRunLoopTimerRef
    • CFRunLoopObserverRef

    我们也知道,OC对象在底层实现都是C语言的结构体,从上面的源码中我们可以找到RunLoop和RunLoopMode在底层的结构体,下面的结构体都被简化过了

    struct __CFRunLoop {
        pthread_t _pthread;
        CFMutableSetRef _commonModes;
        CFMutableSetRef _commonModeItems;
        CFRunLoopModeRef _currentMode;
        CFMutableSetRef _modes;
    };
    struct __CFRunLoopMode {
        CFStringRef _name;
        CFMutableSetRef _sources0;
        CFMutableSetRef _sources1;
        CFMutableArrayRef _observers;
        CFMutableArrayRef _timers;
    };
    
    结合上面的类,我们可以知道,CFRunLoopRef里装了很多Modes,其中有一个是Current,mode里装着Source0,Source1,Timer和Observer,总结一下就跟下图类似,后面我们会介绍一下CFRunLoopModeRef RunLoop

    CFRunLoopModeRef

    • CFRunLoopModeRef代表着RunLoop的运行模式
    • 一个RunLoop包含若干个Mode,每个Mode又包含若干Source0/Source1/Timer/Observers
    • RunLoop启动时只能选择其中一个Mode,作为CurrentMode
    • 如果需要切换Mode,只能退出当前的Loop,再重新选择一个Mode进入
    • 不同组的Source0/Source1/Timer/Observers可以分开,互不影响
    • 如果Mode里没有Source0/Source1/Timer/Observers,RunLoop会立刻退出

    RunLoop运行时有下面几种状态

    CFRunLoopObserveRef

    /* Run Loop Observer Activities */
    typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
        kCFRunLoopEntry = (1UL << 0),
        kCFRunLoopBeforeTimers = (1UL << 1),
        kCFRunLoopBeforeSources = (1UL << 2),
        kCFRunLoopBeforeWaiting = (1UL << 5),
        kCFRunLoopAfterWaiting = (1UL << 6),
        kCFRunLoopExit = (1UL << 7),
        kCFRunLoopAllActivities = 0x0FFFFFFFU
    };
    

    在开发中我们常用的Mode有两种

    1.kCFRunLoopDefaultMode(NSDefaultRunLoopMode):APP的默认Mode,通
    常主线程在这个Mode下运行
    2.UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响

    RunLoop的运行逻辑

    • Source0
      1、触摸时间处理
      2、performSelector:onThread:
    • Source1
      1、Port的线程间通信
      2、系统时间捕捉
    • Timers
      1、NSTimer
      2、performSelector: withObject: afterDelay:
    • Observers
      1、用于监听RunLoop的状态
      2、UI刷新(BeforeWaiting)
      3、Autorelease Pool(BeforeWaiting)
    image.png

    RunLoop的实际运用

      到这里,大家应该对RunLoop的概念有了清晰地认识,下面总结一下RunLoop在实际开发中的应用

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

      首先,控制线程生命周期,在项目中可以理解为,我们开辟子线程做一些比较耗时的工作,等子线程任务完成时候,子线程便会销毁。但是有些情况下的需求是我们不希望子线程那么快的销毁,可能需要子线程等待处理某些事情的时候,我们就可以使用RunLoop,下面是个简单的例子。在这里我们在封装的类内部,手动启动线程,而且不用担心循环引用的问题,使用起来很简单。

    @interface PermenantThread()
    @property(strong, nonatomic)NSThread *thread;
    @end
    
    @implementation PermenantThread
    - (instancetype)init
    {
        self = [super init];
        if (self) {
            self.thread = [[NSThread alloc]initWithBlock:^{
                CFRunLoopSourceContext context;
                CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
                CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
                CFRelease(source);
                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
            }];
            [self.thread start];
        }
        return self;
    }
    - (void)stop{
        CFRunLoopStop(CFRunLoopGetCurrent());
        self.thread = nil;
    }
    - (void)executeTask:(PermenantTask)task{
        task();
    }
    @end
    

      解决timer停止问题,我相信大家在项目中都遇到过。通过上面的了解大家应该都清楚是什么原因造成的timer停止工作。因为RunLoop启动时只能选择一个Mode,ScrollView在滑动时默认为UITrackingRunLoopMode,而定时器默认为kCFRunLoopDefaultMode。所以我们在滑动时候进入Tracking,导致定时器失效。下面是解决方法,但是需要注意的是,NSRunLoopCommonModes并不是一个真的模式,它相当于一个标记,意思是timer在此种标记的模式下都可以运行,UITrackingRunLoopMode,NSDefaultRunLoopMode才是真正存在的模式。

        NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
            //doSomething
        }];
        [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
        [timer fire];
    

      相信大家通过这篇文章对RunLoop有了更清晰的认识和了解,在项目中,RunLoop可以有更多的应用,比如性能优化等。这些东西大家有兴趣可以去翻看相关资料,以便对RunLoop有独到的见解。

    相关文章

      网友评论

          本文标题:RunLoop详解

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