什么是RunLoop ?
- 可以把RunLoop当做是一个无限的死循环,正因为有了这种循环,程序才会在执行完一遍后也不会挂掉,如果没有杀死程序进程,程序会一直监听app中的各种事件(比如触摸事件,定时器事件,selector事件)但不同于死循环的是RunLoop会在事件监听的过程中处于休眠状态,该做事时做事,该休息时休息,这样可以节省cpu资源,提高程序性能
- 一个线程对应一个RunLoop,主线程RunLoop默认已经开启,而子线程的RunLoop需要手动开启(调用run方法)子线程结束时RunLoop销毁
获取RunLoop对象 Foundation
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
获取RunLoop对象 Core Foundation
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); //获得主线程的RunLoop对象
RunLoop结构
- Core Foundation中关于RunLoop的五个类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef - CFRunLoopModeRef代表RunLoop的运行模式
- 一个 RunLoop 包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
- 每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
- 如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
- 这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
CFRunLoopModeRef
系统默认注册了5个mode
- NSDefaultRunLoopMode:App的默认Mode,通常主线程是在这个Mode下运行
- UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
- UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
- NSRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode,从表面上看这是一个models,也就是一个模式的数组包含默认模式和界面跟钟模式,实际上他会处理两种模式下同时产生的各种事件
CFRunLoopTimerRef
CFRunLoopTimerRef是基于时间的触发器
CFRunLoopTimerRef基本上说的就是NSTimer,它受RunLoop的Mode影响
GCD的定时器不受RunLoop的Mode影响
CFRunLoopSourceRef (事件源 输入源)
按照官方文档,Source的分类
Port-Based Sources
Custom Input Sources
Cocoa Perform Selector Sources
CFRunLoopObserverRef
(CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变)
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即将进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), //即将处理Sources
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6), //刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7), //即将退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
给主线程runloop添加观察者
// 1.创建一个监听对象
/*
第一个参数: 告诉系统如何给Observer对象分配存储空间
第二个参数: 需要监听的类型
第三个参数: 是否需要重复监听
第四个参数: 优先级
第五个参数: 监听到对应的状态之后的回调
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
// NSLog(@"%lu", activity);
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"进入RunLoop");
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添加监听
/*
第一个参数:需要监听的RunLoop对象
第二个参数:给指定的RunLoop对象添加的监听对象
第三个参数:在那种模式下监听
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopCommonModes);
//3.释放Observer
CFRelease(observer);
RunLoop处理逻辑-官方版
RunLoop处理逻辑-网友整理
常驻线程
应用场景:开发中要频繁的执行某一个耗时操作,通常会把这个耗时的操作放在子线程中执行,而系统默认子线程中任务执行完后这条子线程就会销毁,
注意 这里千万不要以为只要使用全局属性强引用这条线程就可以继续使用这条线程了,
测试代码: 1/2
- (void)viewDidLoad
{
[super viewDidLoad];
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(demo) object:nil];
self.thread.name = @"我是常驻线程";
[self.thread start];
}
- (void)demo
{
// 获取当前线程
NSLog(@"%@--%d",[NSThread currentThread],__LINE__);
// 获取当前runLoop对象
NSRunLoop *runLoop =[NSRunLoop currentRunLoop];
// 以下代码的目的是为了保证runloop不死 一个NSRunLoop中, 如果没有source或者timer, 那么NSRunLoop就会退出死循环
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
// 运行runLoop对象
[runLoop run];
NSLog(@"如果开启运行循环 我不会被打印出来");
}
打印结果:
测试代码: 2/2
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
// 获取当前线程
NSLog(@"%@--%d",[NSThread currentThread],__LINE__);
// 主线程
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (void)test
{
// 获取当前线程
NSLog(@"%@--%d",[NSThread currentThread],__LINE__);
NSLog(@"已确认可以继续使用这条线程做事");
}
打印结果
**2016-04-07 14:34:37.858 08-RunLoop****应用场景****[3069:184521] <NSThread: 0x7fb60ac08a10>{number = 1, name = main}****--****49**
**2016-04-07 14:34:37.859 08-RunLoop****应用场景****[3069:184863] <NSThread: 0x7fb60af4af30>{number = 2, name = ****我是常驻线程****}****--****57**
**2016-04-07 14:34:37.859 08-RunLoop****应用场景****[3069:184863] ****已确认可以继续使用这条线程做事**