一、RunLoop概念:
一般来讲,一个线程一次只能执行一个任务,执行完成后线程就会退出。如果我们需要一个机制,让线程能随时处理事件但并不退出。
实现这种模型的关键点在于:如何管理事件/消息,如何让线程在没有处理消息时休眠以避免资源占用、在有消息到来时立刻被唤醒。
所以,RunLoop 实际上就是一个对象,这个对象管理了其需要处理的事件和消息,并提供了一个入口函数来执行上面 Event Loop 的逻辑。线程执行了这个函数后,就会一直处于这个函数内部 "接受消息->等待->处理" 的循环中,直到这个循环结束(比如传入 quit 的消息),函数返回。
二、RunLoop与线程的关系:
首先,iOS 开发中能遇到两个线程对象: pthread_t 和 NSThread。过去苹果有份文档标明了 NSThread 只是 pthread_t 的封装,但那份文档已经失效了,现在它们也有可能都是直接包装自最底层的 mach thread。苹果并没有提供这两个对象相互转换的接口,但不管怎么样,可以肯定的是 pthread_t 和 NSThread 是一一对应的。比如,你可以通过 pthread_main_thread_np() 或 [NSThread mainThread] 来获取主线程;也可以通过 pthread_self() 或 [NSThread currentThread] 来获取当前线程。CFRunLoop 是基于 pthread 来管理的。
注意:苹果不允许直接创建 RunLoop,它只提供了两个自动获取的函数:CFRunLoopGetMain() 和 CFRunLoopGetCurrent()。 这两个函数内部的逻辑大概是下面这样
RunLoop 的核心就是一个 mach_msg() ,RunLoop 调用这个函数去接收消息,如果没有别人发送 port 消息过来,内核会将线程置于等待状态。例如你在模拟器里跑起一个 iOS 的 App,然后在 App 静止时点击暂停,你会看到主线程调用栈是停留在 mach_msg_trap() 这个地方。
自动释放池就是根据runloop的原理实现的
回到开始的疑问,为什么要使用RunLoop,一般情况下我们是没必要去启动线程的RunLoop,除非需要在一个单独的线程长久的检测某个事件,类似微信的语音功能,见一个RunLoop专门负责监听说话的线程。看需求而定了。
1.每条线程都有唯一的一个与之对应的RunLoop对象
2.主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
3.RunLoop在第一次获取时创建,在线程结束时销毁
4.获取RunLoop对象:
1)Foundation:
[NSRunLoop currentRunLoop]; // 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop]; // 获得主线程的RunLoop对象
2)Core Foundation:
CFRunLoopGetCurrent(); // 获得当前线程的RunLoop对象
CFRunLoopGetMain(); // 获得主线程的RunLoop对象
三、RunLoop使用场景:
苹果官方文档说明run loop的开启是运用在需要和线程有更多交互的场合上的。
四、概念:
1)Foundation 框架 ——>NSRunLoop
2)Core Foundation ——>CFRunLoopRef
NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装, 所以要了解RunLoop内部结构,需要多研究CFRunLoopRef层面的API (Core Foundation 层面)
五、RunLoop相关类
CoreFoundation中关于RunLoop的5个类
1)CFRunLoopRef ——>NSRunLoop是基于这个类进行封装的
2)CFRunLoopModeRef ——>代表RunLoop的运行模式
一个 RunLoop包含若干个 Mode,每个Mode又包含若干个Source/Timer/Observer
每次RunLoop启动时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode
如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source/Timer/Observer,让其互不影响
系统默认注册了5个Mode:
kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode 影响
UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用
GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
kCFRunLoopCommonModes: 这是一个占位用的Mode,不是一种真正的Mode
3)CFRunLoopSourceRef
4)CFRunLoopTimerRef
5)CFRunLoopObserverRef
六、RunLoop应用
1.NSTimer
2.ImageView显示
3.PerformSelector
4.常驻线程
5.自动释放池
#import "ViewController.h"
@interface ViewController ()
@property (nonatomic, strong) dispatch_source_t timer;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
}
- (void)didReceiveMemoryWarning {
[super didReceiveMemoryWarning];
// Dispose of any resources that can be recreated.
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// // NSRunLoop 主线程对应的RunLoop对象
// NSRunLoop *mainRunLoop = [NSRunLoop mainRunLoop];
// NSLog(@"mainRunLoop = %@", mainRunLoop);
// // NSRunLoop 获得当前方法所在线程对应的RunLoop
// NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
// NSLog(@"currentRunLoop = %@", currentRunLoop);
// // CFRunLoopRef 主线程对应的RunLoop对象
// CFRunLoopRef cfMainRunLoop = CFRunLoopGetMain();
// NSLog(@"cfMainRunLoop = %@", cfMainRunLoop);
// // CFRunLoopRef 获得当前方法所在线程对应的RunLoop
// CFRunLoopRef cfCurrentRunLoop = CFRunLoopGetCurrent();
// NSLog(@"cfCurrentRunLoop = %@", cfCurrentRunLoop);
// // 开启一条子线程
// NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];
// [thread start];
// 创建Observer
/**
* 参数1: 指定如果给Observer分配存储空间
* 参数2: 需要监听的状态类
* kCFRunLoopEntry = (1UL << 0), 即将启动(进入)的时候
* kCFRunLoopBeforeTimers = (1UL << 1), 即将处理timer事件
* kCFRunLoopBeforeSources = (1UL << 2), 即将处理source事件
* kCFRunLoopBeforeWaiting = (1UL << 5), 即将进入睡眠
* kCFRunLoopAfterWaiting = (1UL << 6), RunLoop被唤醒
* kCFRunLoopExit = (1UL << 7), RunLoop退出
* kCFRunLoopAllActivities = 0x0FFFFFFFU 监听所有状态
*
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity 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(@"RunLoop刚从睡眠中唤醒");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop即将退出");
break;
default:
break;
}
});
// 给主线程的RunLoop添加一个观察者,要监听的是RunLoop的哪种运行模式
/**
* 参数1: 需要给哪个RunLoop添加观察者
* 参数2: 需要添加的Observer对象
* 参数3: 在哪种模式下可以监听 kCFRunLoopDefaultMode == NSDefaultRunLoopMode
*/
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
}
- (void)show{
NSLog(@"-------------%s", __func__);
}
- (void)run
{
// 注意: 如果想给子线程添加RunLoop, 不能直接alloc init
// [[NSRunLoop alloc] init]; // 错误
// 只要调用currentRunLoop方法, 系统就会自动创建一个RunLoop, 添加到当前线程中
[NSRunLoop currentRunLoop]; // 这个方法是懒加载
}