RunLoop总结:RunLoop的应用场景(三)滚动视图流畅性优化

作者:哈雷哈雷_Wong

让UITableView、UICollectionView等延迟加载图片。
下面就拿UITableView来举例说明:
UITableView 的 cell 上显示网络图片,一般需要两步,第一步下载网络图片;
第二步,将网络图片设置到UIImageView上。

为了不影响滑动,第一步,我们一般都是放在子线程中来做,这个不做赘述。

第二步,一般是回到主线程去设置。
有了前两篇文章关于Mode的切换,想必你已经知道怎么做了。
就是在为图片视图设置图片时,在主线程设置,并调用

performSelector:withObject:afterDelay:inModes:
方法。最后一个参数,仅设置一个
NSDefaultRunLoopMode

UIImage *downloadedImage = ....;

[self.myImageView performSelector:@selector(setImage:) withObject:downloadedImage afterDelay:0 inModes:@[NSDefaultRunLoopMode]];

当然,即使是读取沙盒或者bundle内的图片,我们也可以运用这一点来改善视图的滑动。但是如果UITableView上的图片都是默认图,似乎也不是很好,你需要自己来权衡了。
有一个非常好的关于设置图片视图的图片,在RunLoop切换Mode时优化的例子:RunLoopWorkDistribution 先看一下界面布局:


一个Cell里有两个Label,和三个imageView,这里的图片是非常高清的(2034 × 1525),一个界面最多有18张图片。为了表现出卡顿的效果,我先自己实现了一下Cell,主要示例代码:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

    static NSString *identifier = @"cellId";

    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];

    if (cell == nil) {

        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];

    }

    for (NSInteger i = 1; i <= 5; i++) {

        [[cell.contentView viewWithTag:i] removeFromSuperview];

    }

    

    UILabel *label = [[UILabel alloc] initWithFrame:CGRectMake(5, 5, 300, 25)];

    label.backgroundColor = [UIColor clearColor];

    label.textColor = [UIColor redColor];

    label.text = [NSString stringWithFormat:@"%zd - Drawing index is top priority", indexPath.row];

    label.font = [UIFont boldSystemFontOfSize:13];

    label.tag = 1;

    [cell.contentView addSubview:label];

    

    UIImageView *imageView = [[UIImageView alloc] initWithFrame:CGRectMake(105, 20, 85, 85)];

    imageView.tag = 2;

    NSString *path = [[NSBundle mainBundle] pathForResource:@"spaceship" ofType:@"jpg"];

    UIImage *image = [UIImage imageWithContentsOfFile:path];

    imageView.contentMode = UIViewContentModeScaleAspectFit;

    imageView.image = image;

    NSLog(@"current:%@",[NSRunLoop currentRunLoop].currentMode);

    [cell.contentView addSubview:imageView];

    

    UIImageView *imageView2 = [[UIImageView alloc] initWithFrame:CGRectMake(200, 20, 85, 85)];

    imageView2.tag = 3;

    UIImage *image2 = [UIImage imageWithContentsOfFile:path];

    imageView2.contentMode = UIViewContentModeScaleAspectFit;

    imageView2.image = image2;

    [cell.contentView addSubview:imageView2];

    

    UILabel *label2 = [[UILabel alloc] initWithFrame:CGRectMake(5, 99, 300, 35)];

    label2.lineBreakMode = NSLineBreakByWordWrapping;

    label2.numberOfLines = 0;

    label2.backgroundColor = [UIColor clearColor];

    label2.textColor = [UIColor colorWithRed:0 green:100.f/255.f blue:0 alpha:1];

    label2.text = [NSString stringWithFormat:@"%zd - Drawing large image is low priority. Should be distributed into different run loop passes.", indexPath.row];

    label2.font = [UIFont boldSystemFontOfSize:13];

    label2.tag = 4;

    

    UIImageView *imageView3 = [[UIImageView alloc] initWithFrame:CGRectMake(5, 20, 85, 85)];

    imageView3.tag = 5;

    UIImage *image3 = [UIImage imageWithContentsOfFile:path];

    imageView3.contentMode = UIViewContentModeScaleAspectFit;

    imageView3.image = image3;

    [cell.contentView addSubview:label2];

    [cell.contentView addSubview:imageView3];


return cell; }

然后在滑动的时候,顺便打印出当前的runloopMode,打印结果是:

2016-12-08 10:34:31.450 TestDemo[3202:1791817] current:UITrackingRunLoopMode

2016-12-08 10:34:31.701 TestDemo[3202:1791817] current:UITrackingRunLoopMode

2016-12-08 10:34:32.184 TestDemo[3202:1791817] current:UITrackingRunLoopMode

2016-12-08 10:34:36.317 TestDemo[3202:1791817] current:UITrackingRunLoopMode

2016-12-08 10:34:36.601 TestDemo[3202:1791817] current:UITrackingRunLoopMode

2016-12-08 10:34:37.217 TestDemo[3202:1791817] current:UITrackingRunLoopMode


可以看出,为imageView设置image,是在 UITrackingRunLoopMode
中进行的,如果图片很大,图片解压缩和渲染肯定会很耗时,那么卡顿就是必然的。

查看实时帧率,我们可以在Xcode 中选择 真机调试
,然后 Product –>Profile–>Core Animation


然后点击开始监测即可:


下面就是帧率:


这里就可以使用先使用上面的方式做一次改进。

[imageView performSelector:@selector(setImage:) withObject:image afterDelay:0 inModes:@[NSDefaultRunLoopMode]];

可以保证在滑动起来顺畅,可是停下来之后,渲染还未完成时,继续滑动就会变的卡顿。在切换到
NSDefaultRunLoopMode
中,一个runloop循环要解压和渲染18张大图,耗时肯定超过50ms(1/60s)。

可以保证在滑动起来顺畅,可是停下来之后,渲染还未完成时,继续滑动就会变的卡顿。在切换到
NSDefaultRunLoopMode
中,一个runloop循环要解压和渲染18张大图,耗时肯定超过50ms(1/60s)。

简单描述一下这种做法:首先创建一个单例,单例中定义了几个数组,用来存要在runloop循环中执行的任务,然后为主线程的runloop添加一个CFRunLoopObserver,当主线程在
NSDefaultRunLoopMode
中执行完任务,即将睡眠前,执行一个单例中保存的一次图片渲染任务。关键代码看 
DWURunLoopWorkDistribution
类即可。

总结:

主线程RunLoop切换到 UITrackingRunLoopMode
时,视图有过多的修改

这也就是上面介绍的RunLoop的使用,避免在主线程RunLoop切换到 UITrackingRunLoopMode
时,修改视图。

RunLoopWorkDistribution链接:

https://github.com/diwu/RunLoopWorkDistribution

如果感觉这篇文章不错可以点击在看:point_down: