32

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

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzI0MTcwNDcyMw%3D%3D&%3Bmid=2247484331&%3Bidx=1&%3Bsn=76894c3a0740f5d7825964483c4e3088
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

作者:哈雷哈雷_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 先看一下界面布局:

zAR3Mr2.jpg!web

一个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

QvmiIzz.jpg!web

然后点击开始监测即可:

fENjAvn.jpg!web

下面就是帧率:

6JfEniu.jpg!web

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

[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

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK