Runloop的原理和使用
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 mode
、modes
、common modes
、common mode items
等属性。其中current mode
和modes
最重要,current mode
表示当前的模式,modes
表示这个Runloop拥有的模式,current mode
可以在modes
的范围中切换。mode
中保存着需要执行的任务,当Runloop被唤醒后,就会去执行current mode
中保存的任务。这里有个问题:如果你只把你的任务放在某一个mode
中,而current mode
并不是你的任务所在的mode
,那么你的任务在Runloop被唤醒时并不会被执行。所以,如果你需要保证你的任务无论什么时候都会被执行,你需要把你的任务放在所有mode
中。
common modes
、common mode items
是为了方便解决上述问题而存在的,它们的机制就是:你可以把一个mode
标记为common mode,一个任务标记为common mode item,标记为common mode item的任务会自动存放在所有common mode中。
通过查看modes
里面的内容,可以知道目前Runloop中可以执行的任务被封装成sources0
、sources1
和timers
三种类型,另外还可以通过添加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