上篇文章讲了Runloop的基本原理和一些需要注意的事项,那今天来说一下Runloop在实际开发中的一些应用场景和更深刻的理解。
大家都知道我们开Timer就会出现耗时操作,那么呢耗时操作肯定不能在主线程里面,以为一旦有手势滑动触摸,UI直接卡死!
所以我们要开辟线程来做这件事,那么开辟线程我们常用的都是比较牛逼的GCD,但是我先用NSThread,这样能更清楚的看到Runloop 的应用。
这里直接上代码:
NSThread * thread = [[NSThread alloc]initWithBlock:^{
NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
//将timer 添加到Runloop里面!
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
}];
[thread start];
然后呢,我在这个timer的方法里面做一个耗时操作,并且我在storyboard里面拖一个textView空间。
- (void)timerMethod{
NSLog(@”come here”);
//耗时操作
[NSThread sleepForTimeInterval:1.1];
static int num = 0;
NSLog(@"%d%@",num,[NSThread currentThread]);
num ++;
}
此时大家可以猜一下,timer的方法会打印么?没错,答案是不会的!
虽然我们开辟了线程,而且创建了timer,也把timer添加到runloop里面了,写的很漂亮,但是,runloop在子线程执行了么?并没有执行!所以我们要让runloop在子线程中执行。那么,为什么要在子线程开启runloop呢?原因是因为线程在线程池里!!一般情况下,线程执行完任务之后cpu就不会再掉用它了!!所以要在线程里面开启runloop !!
//开辟子线程来添加timer
NSThread * thread = [[NSThread alloc]initWithBlock:^{
NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
//将timer 添加到Runloop里面!
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes]; //占位模式
//执行runloop!!!
[[NSRunLoop currentRunLoop]run];
}];
[thread start];
这时候就可以欢快的打印啦,同时我滑动textView,页面也不会卡顿,因为我是开辟子线程来做耗时,一点也不影响主线程的UI操作。
现在,请注意这句代码 :[[NSRunLoop currentRunLoop]run];这是什么啊,run啊,那么runloop一旦跑起来可就是根本停不下来了-死循环啊。也就是说此时我如果这样做一件事:
//开辟子线程来添加timer
NSThread * thread = [[NSThread alloc]initWithBlock:^{
NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
//将timer 添加到Runloop里面!
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes]; //占位模式
//执行runloop!!!
[[NSRunLoop currentRunLoop]run];
NSLog(@"子线程runloop结束了!!");
}];
[thread start];
此时的打印就不会出现,因为run是停不下来的,[[NSRunLoop currentRunLoop]run]; 这句代码下面不会执行了。那我们怎么来结束runloop呢?其实有很多方法:
_finished = NO;
//开辟子线程来添加timer
NSThread * thread = [[NSThread alloc]initWithBlock:^{
NSTimer * timer= [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerMethod) userInfo:nil repeats:YES];
//将timer 添加到Runloop里面!
// [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
// [[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes]; //占位模式
// [[NSRunLoop currentRunLoop]run];
while (!_finished) {
//runloop 执行0.001秒
[[NSRunLoop currentRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:0.0001]];
}
NSLog(@"子线程runloop结束了!!");
先设置一个参数,用来判断什么时候来结束runloop,其实也可以直接暴力的干掉子线程!
下面我再用GCD实现一下:
//创建timer
self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0,dispatch_get_global_queue(0, 0));
//设置timer
dispatch_source_set_timer(self.timer, DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC, 0);
//设置timer回调
dispatch_source_set_event_handler(self.timer, ^{
NSLog(@"-------%@",[NSThread currentThread]);
});
//启动timer
dispatch_resume(self.timer);
熟悉GDC的朋友们知道GCD有代码块,我这里分开写注释是方便理解原理,其实GCD就相当于NSThread+Runloop,内部的原理可能不太准确但是原理基本就是这样的。
其实,还有一种牛逼的方法来实现—ReactiveCocoa 响应机制,简称RAC
首先用cocoapods来导入RAC,这个应该不用再多说了,刚刚说它牛逼在哪呢,GCD的那一坨代码可以用一句代码代替:
[[RACSignal interval:1.0 onScheduler:[RACScheduler scheduler]] subscribeNext:^(NSDate * _Nullable x) {
NSLog(@”—–%@”,[NSThread currentThread]);
}];
非常之屌啊,那其实GCD中的那个应用对runloop里面的source(事件源)来处理的,那么,之前介绍的runloop还有一个observer 观察者啊,那就是我们可以观察runloop的动作,直接上代码:
//获取当前的Runloop
CFRunLoopRef runloop = CFRunLoopGetCurrent();
//定义context runloop 上下文里面的参数 &CFRetain,&CFRelease是因为runloop不遵循ARC所以要进行retain 和relaese
CFRunLoopObserverContext context = {
0,
(__bridge void *)(self),
&CFRetain,
&CFRelease,
NULL
};
// 定义观察者
static CFRunLoopObserverRef defaultModeObserver;
defaultModeObserver = CFRunLoopObserverCreate(NULL, kCFRunLoopBeforeWaiting, YES, 0, &callBack, &context);
//添加当前的runloop的观察者
CFRunLoopAddObserver(runloop, defaultModeObserver, kCFRunLoopCommonModes);
下面是context里面的回调函数
//回调函数
static void callBack(){
}
这就是一个runloop的观察者的一个代码,那么这个有什么用呢,我先设置一个场景:tableViewcell复用的,每个cell需要加载3张很大的图片比如1080px*275px吧,那么屏幕一次就要加载18张图片(按照6s的尺寸),那么我们在滑动的时候界面就会很卡,不流畅。
那么这个时候,我们就可以用runloop的观察者来解决界面卡顿的性能问题:
问题原因: runloop循环一次最多要渲染18张(有可能更多)的图片
解决方案: 一次runloop只叫他渲染1张
那么,我们就要观察runloop的执行情况,观察它只要它执行一次,那我就只加载一张图片,这样就能很好的解决啦
基本的思路就是这样,我这边准备写一个demo,分享出来,会更清晰一点。