[TOC]
NSRunLoop
一. 什么是RunLoop
RunLoop是运动循环,不断跑圈,无限循环。NSRunLoop对应线程,从线程start到线程end,一直在循环检测:
检测inputsource(如点击,双击等操作)同步事件,检测timesource同步事件;
检测到后,会执行处理函数,首先会产生通知,Core Foundation向线程添加RunLoop Observers来监听事件,意在监听事件发生时来做处理。
RunLoop的三大基本作用:
- 他是App持续运行的保证, 保持程序的持续运行 (iOS程序一直活着的原因)
- RunLoop会在循环中处理App的各种事件:
触摸事件, 定时器事件, Selector事件
- RunLoop最大的优势就是能节省CPU的资源, 提高程序的性能, 他会在需要执行任务的时候被唤醒, 当没有任务执行的时候进入休眠状态
主程序的RunLoop
Main函数中的RunLoop, 被称为主运行循环, 而主运行循环在整个App的声明周期中都不会被销毁, 他是程序运行的保证。
程序启动时,系统已经在主线程中加入了Run Loop。它保证了我们的主线程在运行起来后,就处于一种“等待”的状态,这个时候如果有接收到的事件(Timer的定时到了或是其他线程的消息),就会执行任务,否则就处于休眠状态。
首先, 重温一遍App的启动原理
当Main函数执行到UIApplicationMain时, 就开启了RunLoop运行循环
在运行循环开启时, 就会保证程序的持续运行并且处理App的各种事件, 不会退出
// 程序在启动时,第一步就会执行main函数,在main函数中会执行以下操作: int main(int argc, char * argv[]) { @autoreleasepool { /* nil:UIApplication类名或者子类名称,如果为nil,就等于@"UIApplication" NSStringFromClass([AppDelegate class]:UIApplication代理的名称 */ return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } } 程序启动的完整流程 1. 执行main函数 2. 执行UIApplicationMain函数 1> 指定UIApplication对象 2> 指定UIApplication的代理 3. 创建UIApplication对象,并且指定他的代理 //4. 创建一个事件循环:主循环(RunLoop),并且是一个死循环,保证程序的持续运行 5. 加载配置了所有应用程序信息的info.plist文件 1> 判断 Main storyboard file base name中有没有指定Main,即需要加载的StoryBoard文件 2> 如果指定了,就加载Main.storyboard 3> 如果没有指定的话,就会黑屏 6. 应用程序启动完毕
二. NSRunLoop和CFRunLoopRef
在iOS开发中有两套api来访问Runloop:
- Foundation框架:NSRunloop (ObjC)
- Core Foundation框架:CFRunloopRef (C)
NSRunLoop和CFRunLoopRef都代表着RunLoop对象,它们是等价的,可以互相转换;
NSRunLoop是基于CFRunLoopRef的一层ObjC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API。
//Foundation框架【NSRunloop】创建Runloop对象
// 1. 获取当前线程下的Runloop, 懒加载的形式创建
NSRunLoop *curRunLoop = [NSRunLoop currentRunLoop];
NSLog(@"%p", curRunLoop);
// 2. 获取主线程对应的RunLoop对象
NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
NSLog(@"%p", mainRunLoop);
//Core Foundation框架【CFRunloopRef】创建Runloop对象
// 1. 获取当前线程下的Runloop, 懒加载的形式创建
CFRunLoopRef runloop = CFRunLoopGetCurrent();
NSLog(@"%p", runloop);
// 2. 获得主线程的RunLoop对象
CFRunLoopRef cfMainRunLoop = CFRunLoopGetMain();
NSLog(@"%p", cfMainRunLoop);
// 从这里可以看出这两种运行循环是完全不同的对象
// NSRunLoop --> CFRunLoopRef
// 调用getCfRunLoop方法, 将NSRunLoop转化为CFRunLoop
NSLog(@"%p---%p", cfMainRunLoop, mainRunLoop.getCFRunLoop);
RunLoopMode
RunLoopMode: 即RunLoop的运行模式,Runloop要想跑起来,它的内部必须要有一个mode,mode中必须有source/observer/timer,至少要有其中的一个.
==每个RunLoop必须设置一个运行模式==, 当指定(显式或隐式)run loop的运行模式以后,只有与该模式对应的input sources才被监控并允许run loop对事件进行处理, 也只有与该模式对应的observers才会被通知。
RunLoop启动之后, ==只能指定一个运行模式==, 可以使用
currentMode
来获取。如果要切换RunLoop的运行模式, 就要先退出当前的RunLoop, 重新指定Mode再次进入运行。
Core Foundation系统默认注册的5个Mode:
kCFRunLoopDefaultMode: App的默认Mode, 通常主线程是在这个Mode下运行的
UITrackingRunLoopMode: 界面跟踪Mode, 用于ScrollView/TableView等追踪触摸滑动, 保证界面滑动的时候不受其他Mode影响
UIInitializationRunLoopMode: 当App启动时, 第一个进入的Mode, 启动完成之后就不会再使用这个Mode
GSEventReceiveRunLoopMode: 接收系统事件的内部Mode, 通常由系统自动管理
kCFRunLoopCommonMode: 一个类似于占位的Mode, 并不是一个真正的Mode
ObjC系统默认注册的5个Mode:
- NSDefaultRunLoopMode:默认,几乎包括所有输入源(除NSConnection)
- NSRunLoopCommonMode:这是一组可配置的通用模式。将input sources与该模式关联则同时也将input sources与该组中的其它模式进行了关联。
- mode模式:处理modal panels
- connection模式:处理NSConnection事件,属于系统内部,用户基本不用
- event tracking模式:如组件拖动输入源 UITrackingRunLoopModes 不处理定时事件
底层相关类
CFRunloopRef
CFRunloopModeRef【Runloop的运行模式】
CFRunloopSourceRef【Runloop要处理的事件源】
CFRunloopTimerRef【Timer事件】
CFRunloopObserverRef【Runloop的观察者(监听者)】
CFRunLoopSourceRef: 事件源, 事件, 输入等都属于事件源, 他有两个分类
- Source0, 非基于Port, 用户触发的事件, 例如点击事件等
- Source1, 基于Port的事件, 他用于系统内部与线程之间交互
- CFRunLoopTimerRef: 定时器事件:<见下文>
定时器事件
NSTimer:
- 每一个线程都有一个NSRunLoop对象,然而定时器也是在这个对象上面运行的; 但是,当一个线程运行完成了过后,会自动关闭线程,自然NSRunLoop也会被销毁,自然定时器就不会运行;为了不让其线程关闭,用此语句 [[NSRunLoop currentRunLoop]run];那么线程就会保持活跃状态,不会被关闭。
- 如果将NSTimer添加到子线程中, 需要先创建一个RunLoop, 然后再启动RunLoop
NSTimer受到RunLoop的影响, 一般会有一些轻微的误差, 所以对于精密计时, GCD定时器较为精准(GCD定时器不受RunLoop约束,比NSTimer更加准时) ```objective-c NSTimer默认添加到当前NSRunLoop中,也可以手动制定添加到自己新建的NSRunLoop。 //第1种方式:
//此种方式创建的timer默认添加到当前NSRunLoop中 [NSTimer scheduledTimerWithTimeInterval:1.0f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
//保持线程为活动状态,才能保证定时器执行 [[NSRunLoop currentRunLoop] run];//已经将nstimer添加到NSRunloop中了
//第2种方式
//此种方式创建的timer没有添加至runloop中
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0f target:self selector:@selector(timerAction) userInfo:nil repeats:YES];
//将定时器添加到runloop中
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//保持线程为活动状态,才能保证定时器执行
[[NSRunLoop currentRunLoop] run];
```
- GCD定时器: 精确到纳秒, 较为精准
- GCD定时器不受RunLoop约束,比NSTimer更加准时。
- GCD定时器的创建步骤: 创建定时器 -> 设置定时器 -> 设置定时器的回调方法 -> 恢复定时器
- GCD定时器一定要添加一个强引用, 否则会被立即释放
@interface ViewController ()
/** 定时器属性:要强引用定时器,否则创建出来就会被释放*/
@property (nonatomic, strong) dispatch_source_t timer;
@end
int count = 0;
//1.创建定时器
// 获得队列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 创建一个定时器
/*
DISPATCH_SOURCE_TYPE_TIMER 定时器
uintptr_t handle 描述信息
unsigned long mask 传入0
dispatch_queue_t queue 定时器运行的队列,决定定时器在哪个线程中运行。
*/
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
//注意:这里的定时器(dispatch_source_t类型)其实是个OC对象,所以必须强引用"self.timer"。
//2.设置定时器的开始时间,间隔时间
dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)); //晚3秒
uint64_t interval = 1.0 * NSEC_PER_SEC; // 间隔时间 1s
/*设置定时器
dispatch_source_t source, 定时器的对象
dispatch_time_t start, 定时器什么时候开始
uint64_t interval, 定时器多长时间执行一次
uint64_t leeway 精准度,0为绝对精准
*/
dispatch_source_set_timer(self.timer, start, interval, 0);
//3.设置回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"%@", [NSThread currentThread]);
count++;
if (count == 4) {
// 取消定时器
dispatch_cancel(self.timer);
self.timer = nil;
}
});
//4.启动定时器
dispatch_resume(self.timer);
CFRunLoopObserverRef: 观察者
观察者可以观察到RunLoop不同的运行状态
通过判断RunLoop的运行状态, 可以执行一些操作
// 1. 创建监听者 /* CFAllocatorRef allocator 分配存储空间 CFOptionFlags activities 要监听哪个状态,kCFRunLoopAllActivities监听所有状态 Boolean repeats 是否持续监听RunLoop的状态 CFIndex order 优先级,默认为0 Block activity RunLoop当前的状态 */ CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { /* kCFRunLoopEntry = (1UL << 0), 进入工作 kCFRunLoopBeforeTimers = (1UL << 1), 即将处理Timers事件 kCFRunLoopBeforeSources = (1UL << 2), 即将处理Source事件 kCFRunLoopBeforeWaiting = (1UL << 5), 即将休眠 kCFRunLoopAfterWaiting = (1UL << 6), 被唤醒 kCFRunLoopExit = (1UL << 7), 退出RunLoop kCFRunLoopAllActivities = 0x0FFFFFFFU 监听所有事件 */ // 当activity处于什么状态的时候,调用一次 switch (activity) { case kCFRunLoopEntry: NSLog(@"进入"); break; case kCFRunLoopBeforeTimers: NSLog(@"即将处理Timer事件"); break; case kCFRunLoopBeforeSources: NSLog(@"即将处理Source事件"); break; case kCFRunLoopBeforeWaiting: NSLog(@"即将休眠"); break; case kCFRunLoopAfterWaiting: NSLog(@"被唤醒"); break; case kCFRunLoopExit: NSLog(@"退出RunLoop"); break; default: break; } }); // 2. 给对应的RunLoop添加一个监听者,并制定监听的是那种运行模式 /* CFRunLoopRef rl 要添加监听者的RunLoop CFRunLoopObserverRef observer, 要添加的监听者 CFStringRef mode RunLoop的运行模式 */ CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
三. RunLoop与线程
- ==每一条线程, 都有一个与之相对应的RunLoop对象, 负责处理线程中的任务。==
- 线程的创建
- 主线程: RunLoop是在程序已经启动的时候就创建好了, 当程序关闭的时候主线程才被销毁
- 子线程: 子线程==需要手动创建RunLoop==, 并且手动开启, 当没有任务执行时, 该线程会被关闭, RunLoop被销毁
- 子线程和RunLoop
- 子线程会单独开启RunLoop去执行任务
- 子线程和RunLoop是一一对应的关系, 每个子线程都有自己的RunLoop(但需要主动创建)
- ==创建子线程的RunLoop:
[NSRunloop currentRunLoop]
==通过对CFRunLoop原码的分析可以判断出, 这个方法是懒加载获取RunLoop对象的, 当第一次调用这个方法时, 他就会在对应的线程中创建一个RunLoop, 并且保存到一个字典中便于随时取出。也就是说, 如果不主动去获取RunLoop, 那么默认是不会给子线程创建一个RunLoop的。 - ==子线程的RunLoop需要手动开启:
[[NSRunLoop currentRunLoop] run]
== - 如果RunLoop内部没有任何任务需要去处理时, 就会被关闭
// 开启子线程,执行task方法
[NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];
- (void)task {
每一个子线程,都对应一个自己的RunLoop:
[NSRunLoop currentRunLoop] //获取当前子线程的RunLoop
[[NSRunLoop currentRunLoop] run] //开启当前子线程的RunLoop
// 1. 获取当前子线程的RunLoop,主线程的RunLoop
NSLog(@"%p--%p", [NSRunLoop currentRunLoop], [NSRunLoop mainRunLoop]);
// 2. 开启RunLoop
[[NSRunLoop currentRunLoop] run];
}
// 全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef
static CFMutableDictionaryRef loopsDic;
// 访问 loopsDic 时的锁
static CFSpinLock_t loopsLock;
// 获取一个 pthread 对应的 RunLoop
CFRunLoopRef _CFRunLoopGet(pthread_t thread) {
OSSpinLockLock(&loopsLock);
if (!loopsDic) {
// 第一次进入时,初始化全局Dic,并先为主线程创建一个 RunLoop。
loopsDic = CFDictionaryCreateMutable();
CFRunLoopRef mainLoop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, pthread_main_thread_np(), mainLoop);
}
// 直接从 Dictionary 里获取。
CFRunLoopRef loop = CFDictionaryGetValue(loopsDic, thread));
if (!loop) {
// 取不到时,创建一个
loop = _CFRunLoopCreate();
CFDictionarySetValue(loopsDic, thread, loop);
// 注册一个回调,当线程销毁时,顺便也销毁其对应的 RunLoop
_CFSetTSD(..., thread, loop, __CFFinalizeRunLoop);
}
OSSpinLockUnLock(&loopsLock);
return loop;
}
CFRunLoopRef CFRunLoopGetMain() {
return _CFRunLoopGet(pthread_main_thread_np());
}
CFRunLoopRef CFRunLoopGetCurrent() {
return _CFRunLoopGet(pthread_self());
}