文章主要分为四个部分安全
一个线程一次只能执行一个任务,执行完成后线程就会退出。RunLoop 机制能让线程随时处理事件但并不退出。这里说的随时是指:程序须要运行时就保持程序的持续运行,不须要的时候就进入休眠状态。网络
NSRunLoop 和 CFRunLoopRef 都是和RunLoop 机制相关的类。CFRunLoopRef 基于 CoreFoundation 框架内,是纯 C 函数的 API,全部这些 API 都是线程安全的。CFRunLoopRef 的代码是开源的。NSRunLoop 是基于 CFRunLoopRef ,提供了面向对象的 API,可是这些 API 不是线程安全的。app
关于RunLoop 和线程之间的关系要知道如下几点:框架
和 RunLoop 相关的主要涉及五个类:异步
RunLoop的结构函数
从上图能够看出,RunLoop 对象中能够包含多个 Mode,每一个 Mode 又包含多个个 Source、Timer、Observer。oop
关于Mode首先要知道一个RunLoop 对象中可能包含多个Mode,且每次调用 RunLoop 的主函数时,只能指定其中一个 Mode(CurrentMode)。切换 Mode,须要从新指定一个 Mode 。主要是为了分隔开不一样的 Source、Timer、Observer,让它们之间互不影响。测试
总共是有五种Mode:spa
kCFRunLoopDefaultMode
:默认模式,主线程是在这个运行模式下运行UITrackingRunLoopMode
:跟踪用户交互事件(用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其余Mode影响)UIInitializationRunLoopMode
:在刚启动App时第进入的第一个 Mode,启动完成后就再也不使用GSEventReceiveRunLoopMode
:接受系统内部事件,一般用不到kCFRunLoopCommonModes
:伪模式,不是一种真正的运行模式,实际是kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
的结合。有这样一个场景,假设本身封装一个无限轮播视图,颇有可能会出现这样一种状况:当你滑动轮播视图时,轮播视图的定时器再也不起做用,不能经过定时器调整UIScrollView的偏移值。之因此会出项上述现象,是由于主线程的 RunLoop 里有两个 Mode:kCFRunLoopDefaultMode
和 UITrackingRunLoopMode
。默认状况下是defaultMode
,可是当滑动UIScrollView
时,RunLoop 会将 mode 切换为 TrackingRunLoopMode
,这时 Timer 就不会被回。若是想在滑动的时候不让定时器失效,可使用CommonMode来解决。线程
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopSourceRef
CFRunLoopSourceRef是事件源,主要有两种分类方式,一种是苹果官方的分类方式,另外一种是按照函数调用栈栈分类方式。
2.3.1 官方分类
2.3.2 按照函数调用栈分类
CFRunLoopSourceSignal(source)
,将 Source 标记为待处理,而后手动调用 CFRunLoopWakeUp(runloop)
唤醒 RunLoop,让其处理这个事件。函数调用栈分类举例
建立一个按钮,添加点击事件,并在按钮回调事件添加断点,当执行到断点出左侧会出现相关栈调用信息。从上图能够看出:点击事件就是在Sources0
中处理的。至于 Source1
主要是用来接收、分发系统事件,而后再分发到Sources0
中处理。
CFRunLoopTimerRef
CFRunLoopTimerRef
是定时源,你能够简单把它理解为NSTimer
。其包含一个时间点和一个回调(函数指针)。当被加入到 RunLoop 时,RunLoop 会注册对应的时间点,当时间到时,RunLoop 会执行对应时间点的回调。
CFRunLoopObserverRef
CFRunLoopObserverRef
是观察者,主要用来监听RunLoop 的状态,主要有如下几种状态。
能够经过如下代码验证RunLoop的几种状态:
// 建立观察者 CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { NSLog(@"监听到RunLoop发生改变---%zd",activity); }); // 添加观察者到当前RunLoop中 CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); // 释放observer CFRelease(observer);
RunLoop 逻辑流程
上图是笔者从网上找到的一张 RunLoop 运行的相关流程逻辑图。具体来讲主要执行逻辑是这样的:
借助RunLoop能够实现线程后台常驻的功能,关键是在于两行代码,具体请看以下代码。
- (void)viewDidLoad { [super viewDidLoad]; self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(runOne) object:nil]; [self.thread start]; } - (void) runOne{ NSLog(@"----任务1-----"); // 下面两句代码能够实现线程保活 [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; // 测试是否开启了RunLoop,若是开启RunLoop,则来不了这里,由于RunLoop开启了循环。 NSLog(@"未开启RunLoop"); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{ // 利用performSelector,在self.thread的线程中调用run2方法执行任务 [self performSelector:@selector(runTwo) onThread:self.thread withObject:nil waitUntilDone:NO]; } - (void) runTwo{ NSLog(@"----任务2------"); }
实现了上述代码以后,每次点击屏幕都会打印----任务2------,这说明子线程处于活跃状态。
在一些分析AFNetworking
源码的文章中,也常常会出现以下这些代码。其核心也是为了实现线程后台常驻。
+ (void)networkRequestThreadEntryPoint:(id)__unused object { @autoreleasepool { [[NSThread currentThread] setName:@"AFNetworking"]; NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode]; [runLoop run]; } } + (NSThread *)networkRequestThread { static NSThread *_networkRequestThread = nil; static dispatch_once_t oncePredicate; dispatch_once(&oncePredicate, ^{ _networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil]; [_networkRequestThread start]; }); return _networkRequestThread; }
当后台线程执行任务时,经过 performSelector:onThread:..
方法将任务放在后台线程的 RunLoop 中。正常来讲,一个线程执行完任务后就退出了。开启runloop是为了防止线程退出。一方面避免每次请求都要建立新的线程;另外一方面,由于connection 的请求是异步的,若是不开启runloop,线程执行完代码后不会等待网络请求完的回调就退出了,这会致使网络回调的代理方法不执行。
- (void)start { [self.lock lock]; if ([self isCancelled]) { [self performSelector:@selector(cancelConnection) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } else if ([self isReady]) { self.state = AFOperationExecutingState; [self performSelector:@selector(operationDidStart) onThread:[[self class] networkRequestThread] withObject:nil waitUntilDone:NO modes:[self.runLoopModes allObjects]]; } [self.lock unlock]; }
应用程序一旦启动,主线程 RunLoop 里注册了两个 Observer。一个 Observer 监听即将进入Loop事件,回调内会调用 _objc_autoreleasePoolPush() 建立自动释放池,并保证建立释放池发生在其余全部回调以前。另一个 Observer 监视了两个事件(RunLoop即将进入休眠和即将退出 RunLoop 事件) ,前者会调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并建立新池;后者会调用 _objc_autoreleasePoolPop() 来释放自动释放池,并保证释放自动释放池事件发生在其它回调以后。