RunLoop详解

2018-09-04  本文已影响57人  爱看书de图图

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有独到的见解。

上一篇下一篇

猜你喜欢

热点阅读