RunLoop简介
运行循环,在程序运行过程中循环做一些事情,如果没有Runloop程序执行完毕就会立即退出,如果有Runloop程序会一直运行,并且时时刻刻在等待用户的输入操作。RunLoop可以在需要的时候自己跑起来运行,在没有操作的时候就停下来休息。充分节省CPU资源,提高程序性能。
RunLoop作用
1、保持程序持续运行
2、处理App中的各种事件
3、节省CPU资源,提高程序性能
RunLoop和线程间的关系
1、每条线程都有唯一的一个与之对应的RunLoop对象
2、RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
3、主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
4、RunLoop在第一次获取时创建,在线程结束时销毁
RunLoop的结构
主要的成员变量:
CFMutableSetRef _sources0;//基于Port的线程间通信
CFMutableSetRef _sources1;//触摸事件,PerformSelectors
CFMutableArrayRef _observers;//定时器,NSTimer
CFMutableArrayRef _timers;//监听器,用于监听RunLoop的状态一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
RunLoop相关类及作用
CFRunLoopRef:获得当前RunLoop和主RunLoop
CFRunLoopModeRef:RunLoop 运行模式,只能选择一种,在不同模式中做不同的操作
CFRunLoopSourceRef: 事件源,输入源
CFRunLoopTimerRef:定时器时间
CFRunLoopObserverRef:观察者
CFRunLoopModeRef的5种模式
1、kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
2、UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
3、UIInitializationRunLoopMode:在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
4、GSEventReceiveRunLoopMode:接受系统事件的内部 Mode,通常用不到
5、kCFRunLoopCommonModes:这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
CFRunLoopModeRef的应用
解决在ScrollView中,滑动控制器时候定时器失效的方案
/*
创建定时器
*/
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
/*
把定时器加入到runloop中
*重点是Mode*
1、如果Mode使用NSDefaultRunLoopMode:在ScrollView滑动的时候,定时器就会失效,停止滑动的时候又会恢复
2、如果Mode使用UITrackingRunLoopMode:在ScrollView滑动的时候,定时器是正常的,停止滑动的时候就会失效
原因是:当ScrollView滑动的时候,RunLoop的Mode会自动切换成UITrackingRunLoopMode模式,因此timer失效;当停止滑动,RunLoop又会切换回NSDefaultRunLoopMode模式,因此timer又会重新启动。
为什么使用NSRunLoopCommonModes模式就可以滑动和不滑动的时候都正常了呢?因为NSRunLoopCommonModes是一个占位用的Mode,用来标记UITrackingRunLoopMode和kCFRunLoopDefaultMode。因此UITrackingRunLoopMode和kCFRunLoopDefaultMode被标记之后的作用就相同了。
*/
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
CFRunLoopObserverRef的应用
CFRunLoopObserverRef是观察者,能够监听RunLoop的状态改变
//创建监听者
/*
第一个参数 CFAllocatorRef allocator:分配存储空间 CFAllocatorGetDefault()默认分配
第二个参数 CFOptionFlags activities:要监听的状态 kCFRunLoopAllActivities 监听所有状态
第三个参数 Boolean repeats:YES:持续监听 NO:不持续
第四个参数 CFIndex order:优先级,一般填0即可
第五个参数 :回调 两个参数observer:监听者 activity:监听的事件
*/
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop进入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要处理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要处理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒来了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 给RunLoop添加监听者
/*
第一个参数 CFRunLoopRef rl:要监听哪个RunLoop,这里监听的是主线程的RunLoop
第二个参数 CFRunLoopObserverRef observer 监听者
第三个参数 CFStringRef mode 要监听RunLoop在哪种运行模式下的状态
*/
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
/*
CF的内存管理(Core Foundation)
凡是带有Create、Copy、Retain等字眼的函数,创建出来的对象,都需要在最后做一次release
GCD本来在iOS6.0之前也是需要我们释放的,6.0之后GCD已经纳入到了ARC中,所以我们不需要管了
*/
CFRelease(observer);
- CFOptionFlags activities系统提供的枚举
kCFRunLoopEntry = (1UL << 0), // 即将进入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 即将处理Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即将处理Source
kCFRunLoopBeforeWaiting = (1UL << 5), //即将进入休眠
kCFRunLoopAfterWaiting = (1UL << 6),// 刚从休眠中唤醒
kCFRunLoopExit = (1UL << 7),// 即将退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU
RunLoop退出
主线程销毁RunLoop退出
Mode中有一些Timer 、Source、 Observer,这些保证Mode不为空时保证RunLoop没有空转并且是在运行的,当Mode中为空的时候,RunLoop会立刻退出
我们在启动RunLoop的时候可以设置什么时候停止
子线程开启RunLoop
/*
创建子线程并开启
*/
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(show) object:nil];
[thread start];
/*
在子线程中创建RunLoop
*/
-(void)show{
// 注意:打印方法一定要在RunLoop创建开始运行之前,如果在RunLoop跑起来之后打印,RunLoop先运行起来,已经在跑圈了就出不来了,进入死循环也就无法执行后面的操作了。
// 但是此时点击Button还是有操作的,因为Button是在RunLoop跑起来之后加入到子线程的,当Button加入到子线程RunLoop就会跑起来
NSLog(@"%s",__func__);
// 1.创建子线程相关的RunLoop,在子线程中创建即可,并且RunLoop中要至少有一个Timer 或 一个Source 保证RunLoop不会因为空转而退出,因此在创建的时候直接加入
// 添加Source [NSMachPort port] 添加一个端口
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
// 添加一个Timer
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
//创建监听者
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"RunLoop进入");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"RunLoop要处理Timers了");
break;
case kCFRunLoopBeforeSources:
NSLog(@"RunLoop要处理Sources了");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"RunLoop要休息了");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"RunLoop醒来了");
break;
case kCFRunLoopExit:
NSLog(@"RunLoop退出了");
break;
default:
break;
}
});
// 给RunLoop添加监听者
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
// 2.子线程需要开启RunLoop
[[NSRunLoop currentRunLoop]run];
CFRelease(observer);
}
自动释放池
RunLoop内部有一个自动释放池,当RunLoop开启时,就会自动创建一个自动释放池,当RunLoop在休息之前会释放掉自动释放池的东西,然后重新创建一个新的空的自动释放池,当RunLoop被唤醒重新开始跑圈时,Timer,Source等新的事件就会放到新的自动释放池中,当RunLoop退出的时候也会被释放。
重点
创建子线程相关的RunLoop,RunLoop中要至少有一个Timer 或 一个Source 保证RunLoop不会因为空转而退出,因此在创建的时候直接加入,如果没有加入Timer或者Source,或者只加入一个监听者,运行程序会崩溃
- 特别鸣谢:https://www.jianshu.com/p/de752066d0ad
- 本文内容摘自上面的大神
网友评论