RunLoop相关
RunLoop表示 运行循环
基本作用:
- 保存程序的持续运行
- 处理程序中的各种事件(触摸事件、定时器事件、Selector等)
- 节省CPU的资源,提高程序的性能,该做事的时候做事,该休息的时候休息
RunLoop其实是一个死循环,永远都不会结束,由于main函数中就启动了一个RunLoop,这样才能保证程序的持续运行不会退出
RunLoop默认是和主线程相关联的
RunLoop对象:
- iOS中有两套API来访问和使用RunLoop
- Foundation,通过NSRunLoop获得
- Core Foundation,通过CFRunLoopRef获得
NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop基于CFRunLoopRef的一层OC包装,要了解NSRunLoop结构需要多了解CFRunLoopRef层面的API(Core Foundation层面)
RunLoop与线程
- 每一条线程都有一个唯一的与之对应的RunLoop对象
- 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
- RunLoop在第一次获取时创建,在线程结束时销毁
NSRunLoop获得RunLoop
[NSRunLoop currentRunLoop]; //获得当前RunLoop
[NSRunLoop mainRunLoop];//获得主线程RunLoop
CFRunLoopRef获得RunLoop
CFRunLoopGetMain();
CFRunLoopGetCurrent();
从NSRunLoop对象中获取CFRunLoopRef对象
NSRunLoop *nsRunLoop = [NSRunLoop currentRunLoop];
CFRunLoopRef cfRunLoop = [nsRunLoop getCFRunLoop];
创建RunLoop
- (void)newThread
{
[[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil] start];
}
- (void) run
{
//[NSRunLoop currentRunLoop]可以表示当前的RunLoop,也可以创建一个RunLoop,因为它是懒加载模式
NSLog(@"%@",[NSRunLoop currentRunLoop]);
}
RunLoop相关类
Core Foundation中的5个类
- CFRunLoopRef
- CFRunLoopModeRef //RunLoop模式
- CFRunLoopSourceRef //输入源
- CFRunLoopTimerRef //定时器
- CGRunLoopObserverRef //观察者
以上五个类的图形表示关系:
在RunLoop中有多个运行模式,但是RunLoop只能选择一种模式
mode里至少要有一个timer或是source
CFRunLoopModeRef
- CFRunLoopModeRef代表RunLoop的运行模式
- 一个RunLoop包含多个Mode,每个Mode又包含若干个Source / Timer / Observer
- 每次RunLoop启动时,只能指定其中一种Mode,这个Mode称作CurrentMode
- 如果需要切换Mode,只能退出Loop,再重新指定一个Mode进入
- 这样做主要是为了分隔不同组的Source / Timer / Observer,互不影响
系统默认注册了5个Mode:
- kCFRunLoopDefoultMode: App默认Mode,通常主线程在这个Mode下运行
- UITrackingRunLoopMode: 界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
- UIInitializationRunLoopMode 在刚启动App时进入的第一个Mode,启动完成后就不再使用
- GSEventReceiveRunLoopMode 接受系统事件的内部Mode,通常用不到
- kCFRunLoopCommonModes:是一个占位用的Mode,不是一种真正意义的Mode
CFRunLoopTimerRef
添加定时器,模式为NSDefaultRunLoopMode
- (void) timer
{
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
/*
* 参数1 添加一个定时器
* 参数2 runloop的运行模式
*/
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
}
- (void) run
{
NSLog(@"run-----%@------%@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);
}
如果界面上有一个ScrollView,当用户操作的时候,Timer就不起作用了,因为RunLoop运行模式自动切换到UITrackingRunLoopMode界面追踪模式,当用户停止操作后,Timer又开始工作了,RunLoop运行模式自动切换回kCFRunLoopDefoultMode
如果上述代码要在ScrollView滚动的时候定时器才起作用,需要修改代码:
//修改 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; 为:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
如果需要无论ScrollView是否在滚动定时器都起作用,需要修改代码:
笨办法:
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
其实正确的办法:
//NSRunLoopCommonModes = NSDefaultRunLoopMode + UITrackingRunLoopMode
//占用,标签,凡事添加到NSRunLoopCommonModes中的事件都会同时添加到打上common标签的运行模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
另外
[NSTimer scheduledTimerWithTimeInterval:(NSTimeInterval) target:(nonnull id) selector:(nonnull SEL) userInfo:(nullable id) repeats:(BOOL)];
这个方法则不需要创建RunLoop也可以工作,因为内部已经做了处理,但这个方法不能在子线程工作
CFRunLoopSourceRef
CFRunLoopSourceRef 是事件源(输入源)
以前的分类方式:
- Port-Based Sources 基于端口的事件源
- Custom Input Sources 自定义输入源
- Cocoa Perform Selector Sources
现在的分类方式:(分类的依据是函数调用栈)
- Source0:非基于Port(端口),由用户创建的
- Source1:基于Port(端口),由系统内部消息事件
CFRunLoopObserverRef
CFRunLoopObserverRef是观察者,能够监听RunLoop状态的改变
可以监听的事件点有以下几个:
- (void)observer
{
//先手动创建一个监听着
/*
* 参数1:怎么分配存储空间
* 参数2:观察者
* 参数3:时候持续监听
* 参数4:优先级,总是传0
* 参数5:当状态改变时会调用的Block
*/
//CFRunLoopObserverRef observer = CFRunLoopObserverCreate(CFAllocatorRef allocator, CFOptionFlags activities, Boolean repeats, CFIndex order, CFRunLoopObserverCallBack callout, CFRunLoopObserverContext *context);
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
/*
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), //即将进入Loop
kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2),//即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),//刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),//即将退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
*/
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(@"即将退出Loop");
break;
default:
break;
}
});
/*
* 参数1:CFRunLoopRef rl 要监听那个RunLoop
* 参数2:CFRunLoopObserverRef observer 观察者
* 参数3:CFRunLoopMode mode 运行模式
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
//以下枚举等价,只不过一个是OC的,一个是C的
//NSDefaultRunLoopMode == kCFRunLoopDefaultMode
//NSRunLoopCommonModes == kCFRunLoopCommonModes
}