- <h5>初识RunLoop</h5>
- 从字面意义看:运行循环
- <b>基本作用</b>
- 保持程序的运行循环
- 处理app中的各种时间(比如触摸事件、定时器事件、selector事件)
- 节省CPU资源,提高程序性能:该做事时做事,该休息时休息
- . . . . . . .
- <b>main函数中的runLoop</b>
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
- UIApplicationMain函数内部就启动了一个RunLoop
- 所以UIApplication函数一直没有返回,保持了程序的持续运行
- 这个默认启动的RunLoop是跟主线程相关联的
- <h5>RunLoop对象</h5>
- iOS中有2套API来访问和使用RunLoop
- Foundation
- NSRunLoop - Core Foundation
- CFRunLoopRef
- Foundation
- NSRunLoop和CFRunLoopRef都代表着RunLoop对象
- NSRunLoop是基于CFRunLoopRef的一层OC包装,所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API(Core Foundation层面)
- iOS中有2套API来访问和使用RunLoop
- <h5>RunLoop与线程间关系</h5>
- 每条线程都有唯一的一个与之对应的RunLoop对象
- 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
- RunLoop在第一次获取时创建,在线程结束时销毁
- <h5>获得RunLoop对象的方法</h5>
- Foundation
[NSRunLoop currentRunLoop]; //获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; //获得主线程的RunLoop对象 - Core Foundation
CFRunLoopGetCurrent(); //获得当前线程的RunLoop对象
CFRunLoopGetMain(); //获得主线程的RunLoop对象 - 如何转化如上得到的两个RunLoop函数,使其地址一样
mainRunLoop.getCFRunLoop
- Foundation
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[[[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil] start];
}
- (void) run {
// 如何创建子线程对应的RunLoop,比较奇葩
// currentRunLoop本身是懒加载,会在第一次调用的时候判断是否存在,如不存在,自动创建
NSLog(@"%@", [NSRunLoop currentRunLoop]);
NSLog(@"run ---------- %@", [NSThread currentThread]);
}
- <h5>RunLoop的相关类</h5>
- Core Foundation中关于RunLoop的5个类
CFRunLoopRef
CFRunLoopModeRef //RunLoop的运行模式
CFRunLoopSourceRef // 数据源、事件源
CFRunLoopTimerRef //NSTimer
CFRunLoopObserverRef // 观察、监听RunLoop
- Core Foundation中关于RunLoop的5个类
<b>* 注意点:</b>
- 在RunLoop中有多个运行模式,但是RunLoop只能选择一种模式运行
- mode里面至少要有一个timer或者source,observer可有可无(只是用来观察/监听RunLoop)
- <b>CFRunLoopModeRef</b>
- <b>CFRunLoopModeRef代表RunLoop的运行模式</b>
- 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source/Timer/Observer
- 每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称作CurrentMode
- 这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
- <b>系统默认注册了5个Mode</b>
-
kCFRunLoopDefaultMode
:App的默认Mode,通常主线程是在这个Mode下运行(常用) -
UITrackingRunLoopMode
: 界面追踪Mode,用于tableView、ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响(常用) - UIInitializationRunLoopMode:在刚启动App时,进入的第一个mode,启动后就不再使用
- GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
- kCFRunLoopCommonModes:这是一个占位用的Mode,不是真正的Mode
-
- <b>CFRunLoopModeRef代表RunLoop的运行模式</b>
- <h5> CFRunLoopModeRef使用</h5>
- (void)timer {
// 1.创建定时器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
/**
2.添加定时器到RunLoop中,指定runLoop的运行模式
@param NSTimer 定时器
@param Mode runLoop的运行模式
*/
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//UITrackingRunLoopMode: 界面追踪
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode
//占用,标签,凡是添加到NSRunLoopCommonModes中的事件都会被同时添加到打上common的运行模式上
/**
0 : <CFString 0x10af41270 [0x10a0457b0]>{contents =
"UITrackingRunLoopMode"}
2 : <CFString 0x10a065b60 [0x10a0457b0]>{contents = "kCFRunLoopDefaultMode"}
*/
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void) run {
NSLog(@"run -----------%@------%@", [NSThread currentThread], [NSRunLoop currentRunLoop]);
}
- <b>子线程中使用RunLoop的注意点</b>
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
[NSThread detachNewThreadSelector:@selector(timer) toTarget:self withObject:nil];
}
- (void)timer {
// 如果是在子线程中使用此timer创建方式,则需要手动创建runLoop
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
// 该方法内部自动添加到runLoop中,并且设置运行模式为默认
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 子线程中创建runLoop之后不会执行,需要手动开启runLoop
[currentRunLoop run];
}
- <h5>定时器补充—不受RunLoop影响的定时器(GCD定时器)</h5>
@property (nonatomic, strong) dispatch_source_t timer;
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
/**
01.创建GCD中的定时器
@param DISPATCH_SOURCE_TYPE_TIMER source的类型,表示是定时器
@param 0 描述信息,线程ID
@param 0 更详细的描述信息
@param dispatchQueue 队列,确定GCD定时器中的任务在哪个线程中执行
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
/**
02.设置定时器(起始时间|间隔时间|精准度)
@param timer 定时器对象
@param DISPATCH_TIME_NOW 起始时间,DISPATCH_TIME_NOW 从现在开始计时
@param intervalInSeconds * NSEC_PER_SEC 间隔时间
@param leewayInSeconds * NSEC_PER_SEC 精准度,绝对精准0
*/
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
// 03.设置定时器执行的任务
dispatch_source_set_event_handler(timer, ^{
NSLog(@"------------%@", [NSThread currentThread]);
});
// 04.启动执行
dispatch_resume(timer);
self.timer = timer;
}
- <b>注意点:</b>
- 1.GCD的定时器与NSTimer的定时器相比,GCD的定时器时间更为精准
- 2.GCD的定时器完全不受RunLoop Mode的影响
- <h5>CFRunLoopSourceRef</h5>
CFRunLoopSourceRef是事件源(输入源)
-
以前的分法
- Port-Based Sources //基于端口的事件
- Custom Input Sources //自定义事件
- Cocoa Perform Selector Sources //perform Selector
-
现在的分发:(通过函数调用站的方式来分)
- Source0:非基于Port的 //用户触发的事件
- Source1:基于Port的 //系统内部的消息事件
- <h5>CFRunLoopObserverRef</h5>
- (void) observer {
/**
// 01.创建监听者
@param allocator 制定如何分配存储空间
@param activities 要监听的状态 kCFRunLoopAllActivities 所有状态
@param repeats 是否持续监听
@param order 优先级,总是传0
@param observer activity 当状态改变的时候回调
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
/**
kCFRunLoopEntry = (1UL << 0), //即将进入runLoop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理timer事件
kCFRunLoopBeforeSources = (1UL << 2), //即将处理sources事件
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入睡眠
kCFRunLoopAfterWaiting = (1UL << 6), //被唤醒
kCFRunLoopExit = (1UL << 7), //runloop推出
kCFRunLoopAllActivities = 0x0FFFFFFFU //所有状态
*/
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即将进入runLoop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即将处理timer事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即将处理sources事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即将进入睡眠");
break;
case kCFRunLoopExit:
NSLog(@"runloop推出" );
break;
case kCFRunLoopAllActivities:
NSLog(@"所有状态");
break;
default:
break;
}
});
}
-
<h5>RunLoop处理逻辑—官方版</h5>
- <b>RunLoop的事件队列</b>
每次运行run loop,你的线程的run loop会自动处理之前未处理的消息,并通知相关的观察者。具体顺序如下:- 1.通知观察者run loop已经启动
- 2.通知观察者任何即将要开始的定时器
- 3.通知贯彻着任何即将启动的非基于端口的源
- 4.启动任何准备好的非基于端口的源
- 5.如果基于端口的源准备好并处于等待状态,立即启动;并进入步骤9
- 6.通知观察者线程进入休眠
- 7.将线程置于休眠直到任一下面的事件发生:
- 某一事件到达基本端口的源
- 定时器启动
- RunLoop设置的事件已经超时
- RunLoop被显示唤醒
- 8.通知观察者线程被唤醒
- 9.处理为处理的事件
- 如果用户定义的定时器启动,处理定时器事件并重启run loop。进入步骤2
- 如果输入源启动,传递相应的消息
- 如果run loop被显示唤醒而且时间还未超时,重启run loop。进入步骤2
- 10.通知观察者run loop结束
-
<h5>RunLoop处理逻辑—网友整理版</h5>
- <h5>RunLoop应用—常驻线程</h5>
RunLoop在开发中有很多应用场景,比如说:处理NSTimer不工作,ImageView的显示,PerformSelector事件,常驻线程,以及自动释放池等 - <b>常驻线程</b>
@property (nonatomic, strong) NSThread *thread;
// 在storyboard中有两个按钮,创建线程、让线程继续执行任务
//需求:如何保证子线程不挂掉,同时执行其他任务
- (IBAction)creatThreadBtnClick:(id)sender {
// 01.创建线程
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(task1) object:nil];
[self.thread start];
}
- (void)task1 {
NSLog(@"task1-----------%@", [NSThread currentThread]);
// 解决方法(保证子线程不挂掉,同时执行其他任务):RunLoop
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
//保证RunLoop不退出
// 方法一:添加Timer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(runOperation) userInfo:nil repeats:YES];
[runLoop addTimer:timer forMode:NSDefaultRunLoopMode];
// 方法二:添加Source
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
[runLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10]]; // 给runLoop设置时间,10秒钟后runLoop自动退出,(该设置只针对于子线程中的runLoop有用,对主线程中的runLoop没有任何作用)
NSLog(@"--------end---------"); // 10秒钟之后打印该内容
}
- (void)runOperation {
NSLog(@"%s", __func__);
}
- (IBAction)otherOperationBtnClick:(id)sender {
[self performSelector:@selector(task2) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)task2 {
NSLog(@"task2-----------%@", [NSThread currentThread]);
}
- <h5>RunLoop常见面试题</h5>
- 什么是RunLoop?
- 从字面意思看:运行循环、跑圈
- 其实它内部就是do-while循环,在这个循环内部不断地处理各种任务(比如Source、Timer、Observer)
- 一个线程对应一个RunLoop,主线程的RunLoop默认已经启动,子线程的RunLoop得手动启动(调用run方法)
- RunLoop只能选择一个Mode启动,如果当前Mode中没有任何Source(Sources0、Sources1)、Timer,那么就直接退出RunLoop
- 自动释放吃什么时候创建?什么时候释放?
- 第一次创建在:启动runLoop的时候
- 最后一次销毁在:runLoop退出的时候
- 其他时候的创建和销毁:当runLoop即将睡眠的时候销毁之前的释放池,并重新创建一个新的
- 在开发中如何使用RunLoop?什么应用场景?
- 开启一个常驻线程(让一个子线程不进入消亡状态,等待其他线程发来消息,处理其他事件)
- 在子线程中开启一个定时器
- 在子线程中进行一些长期监控
- 可以控制定时器在特定模式下执行
- 可以让某些事件(行为、任务)在特定模式下执行
- 可以添加Observer监听RunLoop的状态,比如监听点击事件的处理(在所有点击事件之前做一些事情)
- 开启一个常驻线程(让一个子线程不进入消亡状态,等待其他线程发来消息,处理其他事件)
- 什么是RunLoop?