RunLoop的基本认识
RunLoop的介绍
- 什么是RunLoop?
- Xcode执行程序的时候就先从Main函数那里开始执行的,Main函数里面实现了UIApplicationMain,而UIApplicationMain里面启动了一个RunLoop对象,所以程序才能一直运行,如果没有RunLoop程序一开始就挂了
- RunLoop内部其实是一个do while 循环 (死循环) 有事件需要处理的时候通知RunLoop 没有事件的时候就让它进入休眠状态
RunLoop的基本作用
- 保持程序的持续运行
- 处理App中的各种事件(如:触摸事件,定时器事件)
- 节省CPU资源,提高程序性能,(如:程序没有任务就进入睡眠状态,有任务的时候就唤醒)
iOS程序中又两套API来访问和使用RunLoop
1.C语言:Core Foundation -> CFRunLoopRef
2.OC语言的:Foundation -> NSRunLoop
-NSRunLoop 其实是 CFRunLoopRef 的OC包装
- RunLoop与线程
1.关系:一个RunLoop对应着一条唯一的线程
2,创建:主线程RunLoop已经创建好了,子线程的RunLoop需要手动创建
3,生命周期:RunLoop在第一次获取时创建,在线程结束时销毁
#如果想让线程不死,可以手动创建一个RunLoop
void msg(int number)
{
NSLog(@"runloop被唤醒");
NSLog(@"执行任务---%d",number);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
do {
NSLog(@"是否有任务需要处理,没有就进入休眠状态");
NSLog(@"runloop进入到休眠状态");
int number = 0;
scanf("%d",&number);
msg(number);
} while (1);
}
return 0;
}
[NSRunLoop currentRunLoop]; //当前的RunLoop
[NSRunLoop mainRunLoop]; //主线程的RunLoop
在子线程需要手动创建RunLoop 创建当前的RunLoop即可
RunLoop是懒加载的
RunLoop是用字典来存储的
RunLoop的相关类
# 五个相关的类
a.CFRunloopRef
b.CFRunloopModeRef【Runloop的运行模式】
c.CFRunloopSourceRef【Runloop要处理的事件源】
d.CFRunloopTimerRef【Timer事件】
e.CFRunloopObserverRef【Runloop的观察者(监听者)】
关系图.png
Mode是运行模式
一个RunLoop包含了若干个模式;
一个运行模型至少要有一个source 或者 Timer
每次RunLoop启动时,只能指定其中一个Mode,这个Mode被称作CurrentMode
如果需要切换Mode ,只能退出线程,重新指定一个Mode进入
-
这样做主要是为了分隔开不同组的 source/Timer/Observer,让它们互不影响
-
系统默认注册了五种Mode
1.kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
2.UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
3.UIInitializationRunLoopMode: 在刚启动App时第进入的第一个 Mode,启动完成后就不再使用
4.GSEventReceiveRunLoopMode:接受系统事件的内部Mode,通常用不到
5.kCFRunLoopCommonModes:这是一个占位用的Mode,不是一种真正的Mode
CFRunloopSourceRef (source事件源)
source0 : 非基于端口的
source1: 基于端口的
可以通过打断点的方式查看一个方法的函数调用栈
CFRunloopTimerRef (Timer事件)
- NSTimer收RunLoop的Mode的影响
- 对应Mode处理对应的事件,增加用户的体验
/*
说明:
(1)runloop一启动就会选中一种模式,当选中了一种模式之后其它的模式就都不鸟。一个mode里面可以添加多个NSTimer,也就是说以后当创建NSTimer的时候,可以指定它是在什么模式下运行的。
(2)它是基于时间的触发器,说直白点那就是时间到了我就触发一个事件,触发一个操作。基本上说的就是NSTimer
*/
- (void)timer1
{
//NSTimer 调用了scheduledTimer方法,那么会自动添加到当前的runloop里面去,而且runloop的运行模式kCFRunLoopDefaultMode
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//更改模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- (void)timer2
{
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//定时器添加到UITrackingRunLoopMode模式,一旦runloop切换模式,那么定时器就不工作
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//定时器添加到NSDefaultRunLoopMode模式,一旦runloop切换模式,那么定时器就不工作
// [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//占位模式:common modes标记
//被标记为common modes的模式 kCFRunLoopDefaultMode UITrackingRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
// NSLog(@"%@",[NSRunLoop currentRunLoop]);
}
- (void)run
{
NSLog(@"---run---%@",[NSRunLoop currentRunLoop].currentMode);
}
- (IBAction)btnClick {
NSLog(@"---btnClick---");
}
GCD定时器不受RunLoop的Mode的影响
GCD定时器的特点:
1,GCD的定时器不会受到RunLoop运行模式的影响
2,可以控制热舞在主线程还是子线程执行
3,GCD定时器比NSTimer更加准确是因为单位不同:GCD单位是纳秒
//1.创建定时器对象
/*
第一个参数:DISPATCH_SOURCE_TYPE_TIMER 创建的是一个定时器
第四个参数:队列,决定在哪个线程中执行任务
*/
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW, 3.0 *NSEC_PER_SEC);
//2.设置定时器(间隔时间|开始时间)
/*
第一个参数:定时器对象
第二个参数:从什么时候开始计时 DISPATCH_TIME_NOW == 从现在开始
第三个参数:间隔时间 2.0 以纳秒为单位
第四个参数:精准度(表示允许的误差)== 0
*/
dispatch_source_set_timer(timer, t, 2.0 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
//3.定义定时器的工作
dispatch_source_set_event_handler(timer, ^{
NSLog(@"----GCD----%@",[NSThread currentThread]);
});
//4.启动执行
dispatch_resume(timer);
//注意:dispatch_source_t本质上是OC类,在这里是个局部变量,需要强引用
self.timer = timer;
.
CFRunloopObserverRef(observer观察者,监听者)
作用: 监听运行循环的状态
如何监听:
1,创建监听对象
2,给RunLoop添加监听者
3,注意对象的释放
//1.创建监听者
/*
第一个参数:分配存储空间
第二个参数:要监听runloop的什么状态
第三个参数:持续监听 == YES
第四个参数:传0
*/
CFRunLoopObserverRef observer =
CFRunLoopObserverCreateWithHandler
(CFAllocatorGetDefault(),kCFRunLoopAllActivities, YES, 0,
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
/*
kCFRunLoopEntry = (1UL << 0), runloop启动
kCFRunLoopBeforeTimers = (1UL << 1), runloop即将处理定时器事件
kCFRunLoopBeforeSources = (1UL << 2), runloop即将处理source事件
kCFRunLoopBeforeWaiting = (1UL << 5), runloop即将休眠
kCFRunLoopAfterWaiting = (1UL << 6), runloop被唤醒
kCFRunLoopExit = (1UL << 7), 退出
kCFRunLoopAllActivities = 0x0FFFFFFFU
*/
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"runloop启动");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"runloop即将处理定时器事件");
break;
case kCFRunLoopBeforeSources:
NSLog(@"runloop即将处理source事件");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"runloop即将休眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"runloop被唤醒");
break;
case kCFRunLoopExit:
NSLog(@"runloop退出");
break;
default:
break;
}
});
//2.设置监听的runloop和运行模式
/*
第一个参数:runloop对象
第二个参数:监听者
第三个参数:runloop的运行模式
第四个参数:
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//3.需要释放
CFRelease(observer);
RunLoop处理逻辑-官方.png
上图显示了线程的输入源
1.基于端口的输入源(Port Sources)
2.自定义输入源(Custom Sources)
3.Cocoa执行Selector的源(performSelectorxxxx
方法)
4.定时源(Timer Sources )
线程针对上面不同的输入源,有不同的处理机制
1.handlePort——处理基于端口的输入源
2.customSrc——处理用户自定义输入源
3.mySelector——处理Selector的源
4.timerFired——处理定时源
RunLoop的应用场
应用场景.png-
常驻线程
-
比如网络请求,语音消息,可以创建一个子线程专门处理语音消息的代码
-
一直在后台做耗时操作,不想让子线程销毁,那么久用一个do while 循环,在do while循环管里面处理事件
-
自动释放池什么时候释放?
-
启动RunLoop的时候会创建自动释放池,RunLoop退出的时候就会让自动释放池销毁
-
当RunLoop进入休眠的时候,就会让自动释放池之前的销毁,然后重新创建一个新的自动释放池(因为在休眠状态,没有事情做,所以自动释放池也没用)
#Author: 会跳舞的狮子