iOS开发大全

Runloop的原理和使用

2022-05-19  本文已影响0人  杨志聪

Runloop是什么

我们从一个"hello world"程序说起:

#include <stdio.h>

int main() {
   printf("hello world\n");
   return 0;
}

很美的一个程序!该程序的特点是,打印完"hello world"后马上就退出了。但是我们日常接触的大部分程序,例如手机应用、电脑应用或者服务器应用,它们启动后,除非用户手动退出,否则会一直保持运行。

为了保持一直运行,不同的应用有不同的方式。而iOS应用的方式,就是Runloop。

Runloop的实现方式

有一定编程基础的人都知道,让程序保持运行其实很简单,不就是一个do {} while(1);死循环吗?但是这不算本事,因为这样的死循环会占尽CPU的时间,对于支持多任务的操作系统来说,这是不能接受的。所以还要想办法让程序在没有工作的时候,让出CPU。那么iOS应用是通过什么方式让出CPU的呢?

使用Xcode运行项目,当app空闲时,可以暂停它来看一看:

mach_msg_trap

可见主线程停留在一个叫mach_msg_trap的函数,这个mach_msg_trap函数就是iOS应用让出CPU的方法。mach_msg_trap是一个属于系统内核的函数,当它被调用后,当前线程就进入休眠,并等待别的线程的消息。当收到别的线程发来的消息后,该线程就会重新唤醒。

所以Runloop的实现方式是:

do {
    // 处理任务
    do_something();
    // 休眠
    mach_msg_trap();
} while (keepLoop == true);

打印Runloop对象

说了这么多,Runloop还是看不见摸不着。我们可以通过Foundation框架的NSRunloop或者CoreFoundation框架的CFRunloop获取到真实的Runloop。我们可以使用NSRunloop为例(NSRunloop其实只是CFRunloop面向对象的封装):

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"%@", [NSRunloop currentRunLoop]);
}

就能将主线程的Runloop对象的内容打印出来了。通过打印的内容,我们可以看到,Runloop对象主要有current modemodescommon modescommon mode items等属性。其中current modemodes最重要,current mode表示当前的模式,modes表示这个Runloop拥有的模式,current mode可以在modes的范围中切换。mode中保存着需要执行的任务,当Runloop被唤醒后,就会去执行current mode中保存的任务。这里有个问题:如果你只把你的任务放在某一个mode中,而current mode并不是你的任务所在的mode,那么你的任务在Runloop被唤醒时并不会被执行。所以,如果你需要保证你的任务无论什么时候都会被执行,你需要把你的任务放在所有mode中。

common modescommon mode items是为了方便解决上述问题而存在的,它们的机制就是:你可以把一个mode标记为common mode,一个任务标记为common mode item,标记为common mode item的任务会自动存放在所有common mode中。

通过查看modes里面的内容,可以知道目前Runloop中可以执行的任务被封装成sources0sources1timers 三种类型,另外还可以通过添加observers来监控Runloop当前的状态。

Runloop的使用

其实在iOS应用开发中,需要使用到Runloop的场景其实很少。Runloop是为了保证线程不退出而设计的,但是通常我们开辟一个线程只是为了处理一些耗时的任务,处理完毕后,该线程就可以退出了。

不过一些著名的开源项目,例如AFNetworking和React Native,就有使用到Runloop来实现其特定功能。

使用Runloop,首先要创建一个线程;线程创建后,在该线程中调用[NSRunLoop currentRunLoop]就能在该线程中创建一个Runloop;由于没有添加任务的Runloop会马上退出,所以还得往Runloop里添加任务;Runloop的任务有三种类型:source0、source1和timer,source0的任务可以通过-[NSObject performSelector:onThread:withObject:waitUntilDone:]来添加;source1的任务可以通过NSPort来添加;timer任务可以通过NSTimer来添加:

#import "ViewController.h"

@interface ViewController ()<NSPortDelegate>

@property (strong, nonatomic) NSPort *port;
@property (strong, nonatomic) NSThread *thread;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self createThread];
}

- (void)createThread {
    self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(createRunloop) object:nil];
    [self.thread start];
}

- (void)createRunloop {
    // 创建runloop
    NSRunLoop *runloop = [NSRunLoop currentRunLoop];
    
    // 监听Runloop的状态
    [self addRunloopObserver];
    
    // 通过NSTimer创建timer类型的任务
    NSTimer *timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerTask:) userInfo:nil repeats:YES];
    [runloop addTimer:timer forMode:NSDefaultRunLoopMode];
    
    // 通过NSPort创建source1类型的任务
    self.port = [[NSPort alloc] init];
    self.port.delegate = self;
    [runloop addPort:_port forMode:NSDefaultRunLoopMode];
    
    [runloop run];
}

// 监听Runloop
- (void)addRunloopObserver {
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        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;
        }
    });
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
}

// 唤醒Runloop执行source1类型的任务
- (void)executeSource1Task {
    [self.port sendBeforeDate:[NSDate now] msgid:0 components:nil from:nil reserved:0];
}

- (void)excuteSource0Task {
    // 通过source0的方式唤醒Runloop并执行任务
    [self performSelector:@selector(source0Task) onThread:self.thread withObject:nil waitUntilDone:NO];
}

- (void)source0Task {
    // 执行source0类型的任务
    // ...
    NSLog(@"do source0 task...");
}

// NSPortDelegate的方法
- (void)handlePortMessage:(NSPortMessage *)message {
    // 执行source1类型的任务
    // ...
    NSLog(@"do source1 task...");
}

- (void)timerTask:(NSTimer *)timer {
    // 执行timer类型的任务
    // ...
    NSLog(@"do timer task");
}

@end

上一篇 下一篇

猜你喜欢

热点阅读