什么是RunLoop
RunLoop:运行循环,在程序运行过程中循环做一些事情
应用范畴:
- 定时器(Timer)、PerformSelector
- GCD Async Main Queue
- 事件响应、手势识别、界面刷新
- 网络请求
- AutoreleasePool
执行完第18行代码后,会即将退出程序
如果有了RunLoop
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
UIApplicationMain中会创建RunLoop对象,RunLoop中的伪代码实现大概是如下的代码:
int retVal = 0;
do{
//睡眠中等待消息
int message = sleep_and_wait();
//处理消息
retVal = process_message(message);
}while(0 == retVal);
- 程序并不会马上退出,而是保持运行状态
- RunLoop的基本作用:
1.保持程序的基本运行
2.处理App中的各种事件(比如触摸事件、定时器事件等)
3.节省CPU资源,提高程序性能:该做事时做事,该休息时休息
......
RunLoop对象
- iOS中有2套API来访问和使用RunLoop
Foundation:NSRunLoop (OC语言)
Core Foundation:CFRunLoopRef (C语言) - NSRunLoop和CFRunLoopRef都代表着RunLoop对象
NSRunLoop是基于CFRunLoopRef的一层OC包装
CFRunLoopRef是开源的:https://opensource.apple.com/tarballs/CF/
RunLoop与线程
- 每条线程都有唯一的一个与之对应的RunLoop对象
- RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
- 线程刚创建时并没有RunLoop对象,RunLoop会在第一次获取它时创建
- RunLoop会在线程销毁时销毁
- 主线程的RunLoop已经自动获取(创建),子线程默认没有开启RunLoop
获取RunLoop对象
- Foundation
[NSRunLoop currentRunLoop];//获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop];//获得主线程的RunLoop对象 - Core Foundation
CFRunLoopGetCurrent();//获得当前线程的RunLoop对象
CFRunLoopGetMain();//获得主线程的RunLoop对象
RunLoop相关的类
-
Core Foundation中关于RunLoop的5个类
CFRunLoopRef
CFRunLoopModeRef
CFRunLoopSourceRef
CFRunLoopTimerRef
CFRunLoopObserverRef
CFRunLoopModeRef
- CFRunLoopModeRef代表RunLoop的运行模式
- 一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer
- RunLoop启动时只能选择其中一个Mode,作为currentMode
- 如果需要切换Mode,只能退出当前Loop,再重新选择一个Mode进入:不同组的Source0/Source1/Timer/Observer能分隔开来,互不影响。
- 如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出
- 常见的2种Mode:
kCFRunLoopDefaultMode (NSDefaultRunLoopMode):App的默认Mode,通常主线程是在这个Mode下运行
UITrackingRunLoopMode:界面跟踪Mode,用于ScrollView追踪触摸滑动,保证界面滑动时不受其他Mode影响
RunLoop的运行逻辑
- Source0
触摸事件处理
performSelector: onThread: - Source1
基于Port的线程间通信
系统事件捕捉 - Timers
NSTimer
performSelector: withObject: afterDelay: - Observers
用于监听RunLoop的状态
UI刷新(BeforeWaiting)
Autorelease pool(BeforeWaiting)
创建添加observer监听runloop的所有状态
第一种方法:
- (void)viewDidLoad {
[super viewDidLoad];
//创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, observeRunLoopActicities, NULL);
//kCFRunLoopCommonModes默认包括kCFRunLoopDefaultMode,UITrackingRunLoopMode
//添加observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
//释放
CFRelease(observer);
}
void observeRunLoopActicities(CFRunLoopObserverRef observer,CFRunLoopActivity activity,void *info){
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"kCFRunLoopEntry");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"kCFRunLoopBeforeTimers");
break;
case kCFRunLoopBeforeSources:
NSLog(@"kCFRunLoopBeforeSources");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"kCFRunLoopBeforeWaiting");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"kCFRunLoopAfterWaiting");
break;
case kCFRunLoopExit:
NSLog(@"kCFRunLoopExit");
break;
default:
break;
}
}
第二种方法:
- (void)viewDidLoad {
[super viewDidLoad];
··先创建一个textview,然后滚动,观察打印结果
//创建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopEntry - %@",mode);
CFRelease(mode);
break;
}
case kCFRunLoopExit:{
CFRunLoopMode mode = CFRunLoopCopyCurrentMode(CFRunLoopGetCurrent());
NSLog(@"kCFRunLoopExit - %@",mode);
CFRelease(mode);
break;
}
default:
break;
}
});
//添加observer到RunLoop中
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopCommonModes);
//释放
CFRelease(observer);
}
RunLoop的运行逻辑
RunLoop休眠的实现原理
RunLoop休眠的细节:
RunLoop休眠和while(1)是不一样的,while(1)虽然也不会继续往下进行执行代码,但是一直在执行死循环,属于线程阻塞,CPU是一直没有休息的;而RunLoop休眠是CPU进入休息状态。有一个比较底层的函数mach_msg()直接进入休眠状态,睡觉,属于内核层面的API,不占用CPU的资源。
RunLoop在实际开发中的应用
- 控制线程生命周期(线程保活,AFNetworking)
利用RunLoop保持子线程一直活在内存中。
新建一个类继承NSThread,重写dealloc方法,方便监听子线程什么时候销毁
LDThread.h文件
#import <Foundation/Foundation.h>
@interface LDThread : NSThread
@end
LDThread.m文件
#import "LDThread.h"
@implementation LDThread
- (void)dealloc{
NSLog(@"%s",__func__);
}
@end
- (void)viewDidLoad {
[super viewDidLoad];
LDThread *thread = [[LDThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
}
- (void)run{
NSLog(@"%s %@",__func__,[NSThread currentThread]);
}
最终打印结果:
-[ViewController run] <LDThread: 0x60000003abc0>{number = 7, name = (null)} //number=7说明是子线程,如果是主线程,number=1
-[LDThread dealloc]
那么如果有需求,当前的子线程不要挂,要一直存在 ,比如touchBegan方法里,点击一次,就在子线程里走一次run方法,代码如下:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
LDThread *thread = [[LDThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
}
- (void)run{
NSLog(@"%s %@",__func__,[NSThread currentThread]);
}
打印结果:
-[ViewController run] <LDThread: 0x600001541f00>{number = 8, name = (null)}
-[LDThread dealloc]
-[ViewController run] <LDThread: 0x60000157c080>{number = 9, name = (null)}
-[LDThread dealloc]
每点击一次,创建一个子线程,实现完毕就会被销毁
现在不希望线程挂掉,有没有什么办法呢?
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
LDThread *thread = [[LDThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[thread start];
}
- (void)run{
NSLog(@"%s %@",__func__,[NSThread currentThread]);
while (1) ;
}
这种方法虽然可以保证子线程不死,但是是已经阻塞了,也没办法做别的事情了,不可取。
那么现在用RunLoop去做,让子线程在有事情做的时候就去做,没事情做的时候就休眠
- (void)viewDidLoad {
[super viewDidLoad];
self.thread = [[LDThread alloc] initWithTarget:self selector:@selector(run) object:nil];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES];
}
//子线程需要执行的任务
- (void)test{
NSLog(@"%s %@",__func__,[NSThread currentThread]);
}
//这个方法的目的:线程保活
- (void)run{
NSLog(@"%s %@",__func__,[NSThread currentThread]);
//往RunLoop里面添加Source/Timer/Observer,这里如果不添加的话,Runloop会直接退出。因为RunLoop里边是空的
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"%s ---end---",__func__);
}
但是上面的方法会造成ViewController和thread都不会被释放,相互引用造成了强引用,所以这里需要完善一下,代码如下所示:
#import "ViewController.h"
#import "LDThread.h"
#import <objc/runtime.h>
@interface ViewController ()
@property (nonatomic,strong) LDThread *thread;
@property (nonatomic,assign,getter = isStoped)BOOL stopped;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.stopped = NO;
self.thread = [[LDThread alloc] initWithBlock:^{
NSLog(@"%@ ---begin---",[NSThread currentThread]);
//往RunLoop里面添加Source/Timer/Observer,这里如果不添加的话,Runloop会直接退出。因为RunLoop里边是空的
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.stopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
// [[NSRunLoop currentRunLoop] run];//这种开启方法开启了无限次循环,它专门用于开启一个永不销毁的线程,所以调用runloopStop也无法停止。如果想手动停止子线程,就不能调用 [[NSRunLoop currentRunLoop] run];
NSLog(@"%@ ---end---",[NSThread currentThread]);
}];
[self.thread start];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
if(!self.thread) return;
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES];
}
//子线程需要执行的任务
- (void)test{
NSLog(@"%s %@",__func__,[NSThread currentThread]);
}
//用于停止子线程的RunLoop
- (void)stop{
//设置标记为YES
self.stopped = YES;
//停止RunLoop
CFRunLoopStop(CFRunLoopGetCurrent());
NSLog(@"%s %@",__func__,[NSThread currentThread]);
//清空线程
self.thread = nil;
}
- (void)dealloc{
NSLog(@"%s",__func__);
//在子线程调用stop(waitUntilDone设置为YES,代表子线程的代码执行完毕后,这个方法才会往下走)
[self performSelector:@selector(stop) onThread:self.thread withObject:nil waitUntilDone:YES];
}
@end
- 解决NSTimer在滑动时停止工作的问题
- (void)viewDidLoad {
[super viewDidLoad];
//不用RunLoop设置mode的话,在滑动tableview时,timer被停止,停止滚动之后才会继续工作。
static int count = 0;
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 repeats:YES block:^(NSTimer * _Nonnull timer) {
NSLog(@"%d",++count);
}];
//设置如下的mode之后,定时器在拖拽tableview时不受影响
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
//NSDefaultRunLoopMode、UITrackingRunLoopMode才是真正存在的模式
//NSRunLoopCommonModes并不是一个真的模式,只是一个标记,timer能在_commonModes数组中存放的模式下工作,_commonModes数组中放着两种模式:NSDefaultRunLoopMode和UITrackingRunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
}
- 监控应用卡顿
- 性能优化
线程的封装(创建一条可控制生命周期的线程)
LDPermenantThread.h文件
#import <Foundation/Foundation.h>
typedef void(^LDPermenantThreadTask)(void);
//这里创建的新类是继承自NSObject的,而不是NSThread的,之所以这样做,是不想外界能直接访问NSThread里边的一些方法,只想让它访问我给提供的接口。
@interface LDPermenantThread : NSObject
/*
开启线程
*/
- (void)run;
/*
在当前子线程执行一个任务
*/
- (void)executeTask:(LDPermenantThreadTask)task;
/*
结束线程
*/
- (void)stop;
@end
LDPermenantThread.m文件
#import "LDPermenantThread.h"
/* LDThread 主要就是用来监听线程周期的,正式用的时候就不用这个类了,直接用NSThread就可以了,这个类在调试的时候监听是否能销毁就可以了**/
@interface LDThread : NSThread
@end
@implementation LDThread
- (void)dealloc{
NSLog(@"%s",__func__);
}
@end
/* LDPermenantThread **/
@interface LDPermenantThread ()
@property (nonatomic,strong) /*LDThread*/NSThread *innerThread;
@property (nonatomic,assign,getter = isStopped) BOOL stopped;
@end
@implementation LDPermenantThread
#pragma mark - public methods
- (instancetype)init{
if (self = [super init]) {
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.innerThread = [[/*LDThread*/NSThread alloc] initWithBlock:^{
[[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode];
while (weakSelf && !weakSelf.isStopped) {
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
}
}];
}
return self;
}
- (void)run{
if (!self.innerThread) return;
[self.innerThread start];
}
- (void)executeTask:(LDPermenantThreadTask)task{
if (!self.innerThread || !task) return;
[self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:nil waitUntilDone:NO];
}
- (void)stop{
if (!self.innerThread) return;
[self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES];
}
- (void)dealloc{
NSLog(@"%s",__func__);
[self stop];
}
#pragma mark - private methods
- (void)__stop{
self.stopped = YES;
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
}
- (void)__executeTask:(LDPermenantThreadTask)task{
task();
}
@end
ViewController.m文件
#import "ViewController.h"
#import "LDPermenantThread.h"
@interface ViewController ()
@property (nonatomic,strong) LDPermenantThread *thread;
@property (nonatomic,strong) UIButton *stopButton;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self.view addSubview:self.stopButton];
self.thread = [[LDPermenantThread alloc] init];
[self.thread run];
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.thread executeTask:^{
NSLog(@"执行任务 - %@",[NSThread currentThread]);
}];
}
//停止按钮的点击事件
- (void)stop{
[self.thread stop];
}
- (void)dealloc{
NSLog(@"%s",__func__);
}
- (UIButton *)stopButton{
if (_stopButton == nil) {
_stopButton = [UIButton buttonWithType:UIButtonTypeCustom];
_stopButton.frame = CGRectMake(100, 100, 100, 50);
[_stopButton setTitle:@"停止" forState:UIControlStateNormal];
[_stopButton setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[_stopButton addTarget:self action:@selector(stop) forControlEvents:UIControlEventTouchUpInside];
}
return _stopButton;
}
@end
上面的是用OC语言,那么用C语言怎么实现呢,init的代码如下:
- (instancetype)init{
if (self = [super init]) {
self.stopped = NO;
__weak typeof(self) weakSelf = self;
self.innerThread = [[/*LDThread*/NSThread alloc] initWithBlock:^{
NSLog(@"begin ----");
//创建上下文(要初始化一下结构体)
CFRunLoopSourceContext context = {0};
//创建source
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
//往RunLoop中添加source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
//销毁source
CFRelease(source);
//启动
while (weakSelf && !weakSelf.isStopped) {
//第三个参数:returnAfterSourceHandled,设置为true,代表执行完source后就会退出当前loop
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
}
//或者写成:
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);//这样就不需要stopped这个属性了。更精简了
NSLog(@"end ----");
}];
}
return self;
}