本文属笔记性质,主要针对自己理解不太透彻的地方进行记录。
推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。
什么是runloop
iOS系统在程序运行过程中循环做一些事情
应用
- 定时器(Timer)、PerformSelector
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool
- 其他
runloop的作用
- 保持程序的持续运行
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
return 0; 则程序会直接退出。
}
}
复制代码
- 处理App中的各种事件(比如触摸事件、定时器事件等)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
- 其他
基本概念
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
- 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
- RunLoop会在线程结束时销毁
- 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
- 任何位置都可以获取主线程RunLoop
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的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会立马退出
常见Mode
- kCFRunLoopDefaultMode(NSDefaultRunLoopMode):
App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:
界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UITrackingCommonMode
会进行
_commonModes
标记,只要_modes
符合_commonModes
都会执行。
_commonModeItems
代表着能在Common模式下工作的单元,比如timer。
Source0
- 触摸事件处理
- performSelector:onThread:
Source1
- 基于Port的线程间通信
- 系统事件捕捉 捕捉之后包装成
Source0
去处理
Timers
- NSTimer
- performSelector:withObject:afterDelay:
底层也是
NSTimer
Observers
- 用于监听RunLoop的状态
- UI自动刷新(BeforeWaiting)
runloop休眠之前刷新
- Autorelease pool(BeforeWaiting)
清理内部需要释放的对象
- 自主监听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 {
// 创建Observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
// 添加Observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
// 释放
CFRelease(observer);
}
复制代码
RunLoop的运行逻辑
- GCD只有在回到主队列时,才会依赖runloop
- (void)viewDidLoad {
[super viewDidLoad];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 处理一些子线程的逻辑
// 回到主线程去刷新UI界面
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"11111111111");
});
});
}
复制代码
- runloop的休眠是内核层级的休眠,而不是while循环这种死循环
控制线程生命周期(线程保活)
- 向自线程runloop中添加一个port,赋予其存在的意义。
线程不会死,也可以执行
test
中的代码。但是run
方法里的end
也不会打印,因为runloop已经休眠了。由于任务永远不会执行完毕,所以哪怕是VC释放了,线程也不会释放。
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[MJThread alloc] initWithTarget:self selector:@sel(run) obj:nil];
[self.thread start];
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:NO];
}
// 子线程需要执行的任务
- (void)test
{
NSLog(@"%s %@", __func__, [NSThread currentThread]);
}
- (void)run {
NSLog(@"%@----begin----", [NSThread currentThread]);
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"%@----end----", [NSThread currentThread]);
}
复制代码
- run
NSRunLoop的run方法是无法停止的,它专门用于开启一个永不销毁的线程(NSRunLoop)。
所以我们哪怕我们手动将runloop停止,也是做不到的。
- runMode
我们可以使用另外一个函数
runMode
来进行一次
runloop循环
但有一个问题是,只要该runloop被唤醒一次,就会退出
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
//[NSDate distantFuture] 会设置一个用不超时的时间
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
复制代码
- while+runMode
通过加标记的方式,让runloop每次处理完时间,都重新runMode一次
self.thread = [[MJThread alloc] initWithBlock:^{
// 往RunLoop里面添加Source\Timer\Observer
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (!weakSelf.isStoped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
复制代码
解决NSTimer在滑动时停止工作的问题
- (void)viewDidLoad {
[super viewDidLoad];
static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d", ++count);
}];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
// NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
// NSRunLoopCommonModes并不是一个真的模式,它只是一个标记
// timer能在_commonModes数组中存放的模式下工作
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
复制代码
performSelector:withObject:afterDelay:
performSelector:withObject:afterDelay:
需要依赖定时器与runloop,所以在自线程中并不会起作用。
- (void)test
{
NSLog(@"2");
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"1");
// 这句代码的本质是往Runloop中添加定时器
[self performSelector:@selector(test) withObject:nil afterDelay:.0];
NSLog(@"3");
});
}
//打印
// 1
// 3
复制代码