Runloop 实际应用和详细解释

来源:互联网 发布:山西网络黄河电视台 编辑:程序博客网 时间:2024/05/29 03:18

上篇文章讲了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,分享出来,会更清晰一点。

原创粉丝点击