OC底层探究 - Runloop

522 阅读6分钟

Runloop

什么是Runloop

孙源 runloop 视频讲解

顾名思义,Runloop就是指运行循环,在程序运行期间循环的做一些事情。

应用范畴:

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

如果没有Runloop,main函数执行完之后就会退出,而有了Runloop之后,程序并不会马上退出,而是保持运行状态。

Runloop的基本作用:

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

Runloop对象

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

  • Foundation:NSRunLoop
  • Core Foundation:CFRunLoopRef

NSRunLoop 和 CFRunLoopRef 都代表着 RunLoop 对象

NSRunLoop 是基于 CFRunLoopRef 的一层OC封装

CFRunLoopRef是开源的,开源地址

Runloop与线程

  • 每条线程都有唯一的一个与之对应的 RunLoop 对象

  • RunLoop 保存在一个全局的 Dictionary 里,线程作为 key,RunLoop 作为 value

  • 线程刚创建时并没有 RunLoop 对象,RunLoop 会在第一次获取它时创建

  • RunLoop 会在线程结束时销毁

  • 主线程的 RunLoop 已经自动获取(创建),子线程默认没有开启 RunLoop

// Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象

// Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象

dispatch_async(dispatch_get_main_queue(), ^{
    // 子线程默认是没有创建Runloop的,在首次获取的时候,自动创建
    [NSRunLoop currentRunLoop];
});

Runloop相关的类

Core Foundation中关于RunLoop的5个类

  • CFRunLoopRef
  • CFRunLoopModeRef
  • CFRunLoopSourceRef
  • CFRunLoopTimerRef
  • CFRunLoopObserverRef

runloop相关类

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 影响

Mode内资源:

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

CFRunLoopObserverRef

我们可以对Runloop进行监听,观察Runloop的运行状态。

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),						// 即将进入Runloop
    kCFRunLoopBeforeTimers = (1UL << 1),		// 即将处理Timer
    kCFRunLoopBeforeSources = (1UL << 2),		// 即将处理Source
    kCFRunLoopBeforeWaiting = (1UL << 5),		// 即将进入休眠
    kCFRunLoopAfterWaiting = (1UL << 6),		// 刚从休眠中唤醒
    kCFRunLoopExit = (1UL << 7),						// 即将退出Runloop
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

//  添加Observer监听RunLoop的所有状态
void observeRunLoopActicities(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;
    }
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
    
    // kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode、UITrackingRunLoopMode
    // 创建Observer
    CFRunLoopObserverRef observer1 = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
    // 添加Observer到RunLoop中
    CFRunLoopAddObserver(CFRunLoopGetMain(), observer1, kCFRunLoopCommonModes);
    // 释放
    CFRelease(observer1);
    
    // 创建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);
    // 释放
    CFRelease(observer);
}

Runloop的运行逻辑

运行逻辑

Runloop在实际开发中的应用

1.控制线程生命周期(线程保活)

@interface ViewController ()
@property (strong, nonatomic) MJThread *thread;
@property (assign, nonatomic, getter=isStoped) BOOL stopped;
@end

void observeRunLoopActicities(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;
    }
}

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    __weak typeof(self) weakSelf = self;
    
    self.stopped = NO;
    self.thread = [[MJThread alloc] initWithBlock:^{
        NSLog(@"%@----begin----", [NSThread currentThread]);
        
        // 往RunLoop里面添加Source\Timer\Observer
        [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
        CFRunLoopObserverRef observer1 = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
        // 添加Observer到RunLoop中
        CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer1, kCFRunLoopCommonModes);
        // 释放
        CFRelease(observer1);
      	// 这里要先判断self是否已经被释放了
        while (weakSelf && !weakSelf.isStoped) {
            NSLog(@"哈哈哈");
            // 在这里重新进入Runloop
            [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
        }
        
        NSLog(@"%@----end----", [NSThread currentThread]);
        
        // NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)
        // [[NSRunLoop currentRunLoop] run];
        /*
         it runs the receiver in the NSDefaultRunLoopMode by repeatedly invoking runMode:beforeDate:.
         In other words, this method effectively begins an infinite loop that processes data from the run loop’s input sources and timers
         */
        
    }];
    [self.thread start];
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    [self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}

// 子线程需要执行的任务
- (void)test
{
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

- (IBAction)stop {
    // 已经停止以后,就不能再调用
    if (!self.stopped) {
      	// 在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
        [self performSelector:@selector(stopThread) onThread:self.thread withObject:nil waitUntilDone:YES];
    }
}

// 用于停止子线程的RunLoop
- (void)stopThread
{
    // 设置标记为NO
    self.stopped = YES;
    
    // 停止RunLoop
    CFRunLoopStop(CFRunLoopGetCurrent());
    NSLog(@"%s %@", __func__, [NSThread currentThread]);
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    // 如果waitUntilDone为NO,那么会出现坏内存访问,
    // 因为这个时候主线程和子线程是同时执行的,子线程在执行代码的时候控制器已经被销毁了
    [self stop];
}

@end
线程保活封装OC
/** MJThread **/
@interface MJThread : NSThread
@end
@implementation MJThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

/** MJPermenantThread **/
@interface MJPermenantThread()
@property (strong, nonatomic) MJThread *innerThread;
@property (assign, nonatomic, getter=isStopped) BOOL stopped;
@end

@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init
{
    if (self = [super init]) {
        self.stopped = NO;
        
        __weak typeof(self) weakSelf = self;
        
        self.innerThread = [[MJThread alloc] initWithBlock:^{
            [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
            
            while (weakSelf && !weakSelf.isStopped) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
            }
        }];
        
        [self.innerThread start];
    }
    return self;
}

//- (void)run
//{
//    if (!self.innerThread) return;
//
//    [self.innerThread start];
//}

- (void)executeTask:(MJPermenantThreadTask)task
{
    if (!self.innerThread || !task) return;
    
    [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

- (void)stop
{
    if (!self.innerThread) return;
    
    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [self stop];
}

#pragma mark - private methods
- (void)__stop
{
    self.stopped = YES;
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}

- (void)__executeTask:(MJPermenantThreadTask)task
{
    task();
}

@end
线程保活封装C语言
/** MJThread **/
@interface MJThread : NSThread
@end
@implementation MJThread
- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

/** MJPermenantThread **/
@interface MJPermenantThread()
@property (strong, nonatomic) MJThread *innerThread;
@end

@implementation MJPermenantThread
#pragma mark - public methods
- (instancetype)init
{
    if (self = [super init]) {
        self.innerThread = [[MJThread alloc] initWithBlock:^{
            NSLog(@"begin----");
            
            // 创建上下文(要初始化一下结构体)
            CFRunLoopSourceContext context = {0};
            
            // 创建source
            CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
            
            // 往Runloop中添加source
            CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
            
            // 销毁source
            CFRelease(source);
            
            // 启动
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
            
//            while (weakSelf && !weakSelf.isStopped) {
//                // 第3个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop
//                CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
//            }
            
            NSLog(@"end----");
        }];
        
        [self.innerThread start];
    }
    return self;
}

- (void)executeTask:(MJPermenantThreadTask)task
{
    if (!self.innerThread || !task) return;
    
    [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO];
}

- (void)stop
{
    if (!self.innerThread) return;
    
    [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
    
    [self stop];
}

#pragma mark - private methods
- (void)__stop
{
    CFRunLoopStop(CFRunLoopGetCurrent());
    self.innerThread = nil;
}

- (void)__executeTask:(MJPermenantThreadTask)task
{
    task();
}

@end

2.解决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];

3.监控应用卡顿

可以添加 Observer 到主线程 RunLoop 中,通过监听 RunLoop 状态切换的耗时,以达到监控卡顿的目的

// 创建观察者
CFRunLoopObserverContext context = {0,(__bridge void*)self,NULL,NULL};
runLoopObserver = CFRunLoopObserverCreate(kCFAllocatorDefault,kCFRunLoopAllActivities,YES,0,&runLoopObserverCallBack,&context);
// 添加观察者到runloop的common模式
CFRunLoopAddObserver(CFRunLoopGetMain(), _observer, kCFRunLoopCommonModes);

//创建子线程监控
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    //子线程开启一个持续的 loop 用来进行监控
    while (YES) {
        long semaphoreWait = dispatch_semaphore_wait(dispatchSemaphore, dispatch_time(DISPATCH_TIME_NOW, 3 * NSEC_PER_SEC));
        if (semaphoreWait != 0) {
            if (!runLoopObserver) {
                timeoutCount = 0;
                dispatchSemaphore = 0;
                runLoopActivity = 0;
                return;
            }
            //BeforeSources 和 AfterWaiting 这两个状态能够检测到是否卡顿
            if (runLoopActivity == kCFRunLoopBeforeSources || runLoopActivity == kCFRunLoopAfterWaiting) {
                //将堆栈信息上报服务器的代码放到这里
            } //end activity
        }// end semaphore wait
        timeoutCount = 0;
    }// end while
});

面试题

  1. 讲讲 RunLoop,项目中有用到吗?

  2. runloop内部实现逻辑?

  3. runloop和线程的关系?

    线程与runloop是一对一的关系,主线程默认开启runloop,子线程刚创建时并不会开启runloop,而是在第一次获取runloop的时候开启。

  4. timer 与 runloop 的关系?

    runloop来控制timer什么时候执行。

  5. 程序中添加每3秒响应一次的NSTimer,当拖动tableview时timer可能无法响应要怎么解决?

    将 timer 添加到 Runloop,并设置为 commonMode

    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
    
  6. runloop 是怎么响应用户操作的, 具体流程是什么样的?

    1. Source1 捕捉用户事件
    2. 然后交给 Source0 去处理
  7. 说说runLoop的几种状态

    • kCFRunLoopEntry // 即将进入Runloop
    • kCFRunLoopBeforeTimers // 即将处理Timer
    • kCFRunLoopBeforeSources // 即将处理Source
    • kCFRunLoopBeforeWaiting // 即将进入休眠
    • kCFRunLoopAfterWaiting // 刚从休眠中唤醒
    • kCFRunLoopExit // 即将退出Runloop
  8. runloop的mode作用是什么?

    隔离不同mode内的资源(source、timer),只需要处理一种模式下的资源,提高流畅度,节省资源。