[iOS开发]UITableView的性能优化

这篇具有很好参考价值的文章主要介绍了[iOS开发]UITableView的性能优化。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

一些基础的优化


(一)CPU

1. 用轻量级对象

比如用不到事件处理的地方,可以考虑使用 CALayer 取代 UIView

CALayer * imageLayer = [CALayer layer];
imageLayer.bounds = CGRectMake(0,0,200,100);
imageLayer.position = CGPointMake(200,200);
imageLayer.contents = (id)[UIImage imageNamed:@"xx.jpg"].CGImage;
imageLayer.contentsGravity = kCAGravityResizeAspect;
[tableCell.contentView.layer addSublayer:imageLayer];

2. 不要频繁地调用UIView的相关属性

比如 frameboundstransform 等属性,尽量减少不必要的修改
不要给UITableViewCell动态添加subView,可以在初始化UITableViewCell的时候就将所有需要展示的添加完毕,然后根据需要来设置hidden属性显示和隐藏

3. 提前计算好布局

在滑动时,会不断调用heightForRowAtIndexPath:,当Cell高度需要自适应时,每次回调都要计算高度,会导致UI卡顿。为了避免重复无意义的计算,需要缓存高度。
UITableViewCell高度计算主要有两种,一种固定高度,另外一种动态高度。
固定高度:
rowHeight高度默认44
对于固定高度直接采用self.tableView.rowHeight = 77tableView:heightForRowAtIndexPath:更高效
动态高度:
采用tableView:heightForRowAtIndexPath:这种代理方式,设置这种代理之后rowHeight则无效,需要满足以下三个条件

  • 使用Autolayout进行UI布局约束(要求cell.contentView的四条边都与内部元素有约束关系)
  • 指定TableView的estimatedRowHeight属性的默认值
  • 指定TableView的rowHeight属性为UITableViewAutomaticDimension
self.tableView.rowHeight = UITableViewAutomaticDimension;
self.tableView.estimatedRowHeight = 44;

除了提高cell高度的计算效率之外,对于已经计算出的高度,我们需要进行缓存

4. 直接设置frame

Autolayout 会比直接设置 frame 消耗更多的 CPU 资源

5. 图片尺寸合适

图片的 size 最好刚好跟 UIImageViewsize 保持一致
图片通过contentMode处理显示,对tableview滚动速度同样会造成影响
从网络下载图片后先根据需要显示的图片大小切/压缩成合适大小的图,每次只显示处理过大小的图片,当查看大图时在显示大图。
服务器直接返回预处理好的小图和大图以及对应的尺寸最好

/// 根据特定的区域对图片进行裁剪
+ (UIImage*)kj_cutImageWithImage:(UIImage*)image Frame:(CGRect)cropRect{
    return ({
        CGImageRef tmp = CGImageCreateWithImageInRect([image CGImage], cropRect);
        UIImage *newImage = [UIImage imageWithCGImage:tmp scale:image.scale orientation:image.imageOrientation];
        CGImageRelease(tmp);
        newImage;
    });
}

6. 控制最大并发数量

控制一下线程的最大并发数量,当下载线程数超过2时,会显著影响主线程的性能。可以用一个NSOperationQueue来维护下载请求,并设置其最大线程数maxConcurrentOperationCount
当然在不需要响应用户请求时,也可以增加下载线程数来加快下载速度:

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate{
    if (!decelerate) self.queue.maxConcurrentOperationCount = 5;
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    self.queue.maxConcurrentOperationCount = 5;
}
- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView{
    self.queue.maxConcurrentOperationCount = 2;
}

7. 子线程处理

尽量把耗时的操作放到子线程

  • 文本处理(尺寸计算、绘制)
  • 图片处理(解码、绘制)

8. 异步绘制

异步绘制,就是异步在画布上绘制内容,将复杂的绘制过程放到后台线程中执行,然后在主线程显示。

// 异步绘制,切换至子线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
    UIGraphicsBeginImageContextWithOptions(size, NO, scale);
    CGContextRef context = UIGraphicsGetCurrentContext();
    // TODO:draw in context...
    CGImageRef imgRef = CGBitmapContextCreateImage(context);
    UIGraphicsEndImageContext();
    dispatch_async(dispatch_get_main_queue(), ^{
        self.layer.contents = imgRef;
    });
});

ios如何优化tableview,ios,性能优化

(二)GPU

1. 避免短时间内大量显示图片

尽可能将多张图片合成一张进行显示。

2. 控制尺寸

GPU能处理的最大纹理尺寸是4096x4096,超过这个尺寸就会占用CPU资源进行处理,所以纹理尽量不要超过这个尺寸。

3. 减少图层混合操作

当多个视图叠加,放在上面的视图是半透明的,那么这个时候GPU就要进行混合,把透明的颜色加上放在下面的视图的颜色混合之后得出一个颜色再显示在屏幕上,这一步是消耗GPU资源

  • UIViewbackgroundColor不要设置为clearColor,最好设置和superViewbackgroundColor颜色一样
  • 图片避免使用带alpha通道的图片

4. 透明处理

减少透明的视图,不透明的就设置opaque = YES

5. 避免离屏渲染

离屏渲染就是在当前屏幕缓冲区以外,新开辟一个缓冲区进行操作。离屏渲染的整个过程,需要多次切换上下文环境,先是从当前屏幕切换到离屏;等到离屏渲染结束以后,将离屏缓冲区的渲染结果显示到屏幕上,又需要将上下文环境从离屏切换到当前屏幕。

(1)下面的情况或操作会引发离屏渲染
  • 光栅化,layer.shouldRasterize = YES
  • 遮罩,layer.mask
  • 圆角,同时设置 layer.masksToBounds = YESlayer.cornerRadius > 0
  • 阴影,layer.shadow
  • layer.allowsGroupOpacity = YESlayer.opacity != 1
  • 重写drawRect方法
(2)圆角优化

这里主要其实就是解决同时设置layer.masksToBounds = YESlayer.cornerRadius > 0就会产生的离屏渲染。其实我们在使用常规视图切圆角时,可以只使用view.layer.cornerRadius = 3.0,这时是不会产生离屏渲染。但是UIImageView有点特殊,切圆角时必须上面2句同时设置,则会产生离屏渲染,所以我们可以考虑通过 CoreGraphics 绘制裁剪圆角,或者叫美工提供圆角图片。

- (UIImage *)billy_ellipseImage {
    UIGraphicsBeginImageContextWithOptions(self.size, NO, 0.0);
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
    CGContextAddEllipseInRect(ctx, rect);
    CGContextClip(ctx);
    [self drawInRect:rect];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}

此外,还可以通过贝塞尔曲线画圆角:

- (void)clipCornerWithImageView:(UIImageView *)originView
                     andTopLeft:(BOOL)topLeft
                    andTopRight:(BOOL)topRight
                  andBottomLeft:(BOOL)bottomLeft
                 andBottomRight:(BOOL)bottomRight
                   cornerRadius:(CGFloat)radius
{
    CGRect rect = originView.bounds;
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect cornerRadius:radius];
    // 创建遮罩层
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    maskLayer.frame = rect;
    maskLayer.path = maskPath.CGPath;   // 轨迹
    originView.layer.mask = maskLayer;
}

- (void)clipCornerWithImageView:(UIImageView *)originView
                    cornerRadius:(CGFloat)radius {
    CGRect rect = originView.bounds;
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(radius, radius)];
    // 创建遮罩层
    CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];
    maskLayer.frame = rect;
    maskLayer.path = maskPath.CGPath;   // 轨迹
    originView.layer.mask = maskLayer;
}

这样还可以控制特定角是否设置圆角。这种情况有个弊端,就是切割角度有限,所以实现大角度圆角只能采取自己画线的方式来操作。

(3)阴影优化

对于shadow,如果图层是个简单的几何图形或者圆角图形,我们可以通过设置shadowPath来优化性能,能大幅提高性能。

imageView.layer.shadowColor = [UIColor grayColor].CGColor;
imageView.layer.shadowOpacity = 1.0;
imageView.layer.shadowRadius = 2.0;
UIBezierPath *path = [UIBezierPath bezierPathWithRect:imageView.frame];
imageView.layer.shadowPath = path.CGPath;
(4)强制开启光栅化

当图像混合了多个图层,每次移动时,每一帧都要重新合成这些图层,十分消耗性能,这时就可以选择强制开启光栅化layer.shouldRasterize = YES
当我们开启光栅化后,会在首次产生一个位图缓存,当再次使用时候就会复用这个缓存,但是如果图层发生改变的时候就会重新产生位图缓存。
所以这个功能一般不能用于UITableViewCell中,复用反而降低了性能。最好用于图层较多的静态内容的图形。

(5)优化建议
  • 使用中间透明图片蒙上去达到圆角效果
  • 使用ShadowPath指定layer阴影效果路径
  • 使用异步进行layer渲染
  • 将UITableViewCell及其子视图的opaque属性设为YES,减少复杂图层合成
  • 尽量使用不包含透明alpha通道的图片资源
  • 尽量设置layer的大小值为整形值
  • 背景色的alpha值应该为1,例如不要使用clearColor
  • 直接让美工把图片切成圆角进行显示,这是效率最高的一种方案
  • 很多情况下用户上传图片进行显示,可以让服务端处理圆角

加载图片的特殊需求


对于没有大型项目经验的我,很难触碰到设备的性能瓶颈,可是未来接触的项目里需要处理的数据会有很多,可能会有各种特殊的需求,比如要求实现: 1. 要求 `tableView` 滚动的时候,滚动到哪行,哪行的图片才加载并显示,滚动过程中图片不加载显示; 2. 页面跳转的时候,取消当前页面的图片加载请求;

先来看看一般的加载逻辑,放一段我之前写过的项目的代码:
ios如何优化tableview,ios,性能优化

如上设置,如果我们有20行cell,页面启动的时候,直接滑动到最底部,20个cell都进入过了界面,- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath 这个方法就会被调用20次,不符合需求1。
为此,我学习了一下,学习到了两个解决方案,并自己动手实践了一下:

Runloop的小技巧

runloop - 两种常用模式介绍: trackingMode && defaultRunLoopMode

  • 默认情况 - defaultRunLoopMode
  • 滚动时候 - trackingMode

滚动的时候,进入trackingMode,这会导致defaultMode下的任务会被暂停,停止滚动的时候再次进入defaultMode并继续执行defaultMode下的任务。
代码:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (!cell) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }
    
    DemoModel *model = self.datas[indexPath.row];
    cell.textLabel.text = model.text;
   
    
    if (model.iconImage) {
        cell.imageView.image = model.iconImage;
    } else {
        cell.imageView.image = [UIImage imageNamed:@"placeholder.png"];
        [self performSelector:@selector(billy_loadImgeWithIndexPath:)
                   withObject:indexPath
                   afterDelay:0.0
                      inModes:@[NSDefaultRunLoopMode]];
    }
    return cell;
}

//下载图片,并渲染到cell上显示
- (void)billy_loadImgeWithIndexPath:(NSIndexPath *)indexPath {
    
    DemoModel *model = self.datas[indexPath.row];
    UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
    
    [ImageDownLoadManager runloop_loadImageWithModel:model success:^{
        //主线程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            cell.imageView.image = model.iconImage;
            //[cell layoutSubviews];
        });
    }];
}

其他办法

我们可以手动判断UITableView的状态,保存下载任务,然后决定执行哪些下载任务或者在适当的时机取消这些任务。

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:@"cell"];
    if (!cell) {
        cell = [[UITableViewCell alloc]initWithStyle:UITableViewCellStyleDefault reuseIdentifier:@"cell"];
    }
    
    DemoModel *model = self.datas[indexPath.row];
    cell.textLabel.text = model.text;
   
    
    if (model.iconImage) {
        cell.imageView.image = model.iconImage;
    } else {
        cell.imageView.image = [UIImage imageNamed:@"placeholder.png"];

//        [self performSelector:@selector(billy_loadImgeWithIndexPath:)
//                   withObject:indexPath afterDelay:0.0
//                      inModes:@[NSDefaultRunLoopMode]];

        //拖动的时候不显示
        if (!tableView.dragging && !tableView.decelerating) {
            //下载图片数据
            [self billy_loadImgeWithIndexPath:indexPath];
        }
    }
    return cell;
}
- (void)billy_loadImgeWithIndexPath:(NSIndexPath *)indexPath {
    
    DemoModel *model = self.datas[indexPath.row];
    //保存当前正在下载的操作
    ImageDownLoadManager *manager = self.imageLoadDic[indexPath];
    if (!manager) {
        
        manager = [[ImageDownLoadManager alloc] init];
        //开始加载-保存到当前下载操作字典中
        [self.imageLoadDic setObject:manager forKey:indexPath];
    }
    
    [manager loadImageWithModel:model success:^{
        //主线程刷新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
            cell.imageView.image = model.iconImage;
            [cell layoutSubviews];
        });
        
        //加载成功-从保存的当前下载操作字典中移除
        [self.imageLoadDic removeObjectForKey:indexPath];
    }];
}
- (void)billy_loadImage {
    //拿到界面内-所有的cell的indexpath
    NSArray *visableCellIndexPaths = self.tableView.indexPathsForVisibleRows;
    for (NSIndexPath *indexPath in visableCellIndexPaths) {
        DemoModel *model = self.datas[indexPath.row];
        if (model.iconImage) {
            continue;
        }
        [self billy_loadImgeWithIndexPath:indexPath];
    }
}
- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
    if (!decelerate) {
        //直接停止-无动画
        [self billy_loadImage];
    }
}
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView{
    [self billy_loadImage];
}

界面消失:

- (void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];
    NSArray *loadImageManagers = [self.imageLoadDic allValues];
    //当前图片下载操作全部取消
    [loadImageManagers makeObjectsPerformSelector:@selector(cancelLoadImage)];
}

下面附上我demo的地址:https://github.com/BillyMiracle/TableViewImgLoadOptimization。欢迎大家下载一起学习。

先写这么多吧,学习的道路还长着呢。。。文章来源地址https://www.toymoban.com/news/detail-847156.html

到了这里,关于[iOS开发]UITableView的性能优化的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • iOS UITableView上拉加载解决偶然跳动的Bug

    RPReplay_Final1693296737 解决方案 1.列表使用固定行高时,设置:

    2024年02月10日
    浏览(26)
  • iOS数据采集方案-UITableView和UICollectionView点击事件实现

    在移动端开发中,数据采集是非常重要的一环,它能够帮助我们了解用户的行为和使用情况,从而进行产品优化和决策制定。在iOS应用中,全埋点是一种常用的数据采集方式,它可以实现对用户在应用中的各种操作进行监测和记录。本文将介绍如何在iOS应用中通过全埋点实现

    2024年02月06日
    浏览(34)
  • iOS长图生成的pdf性能优化记录

    背景   某日产品拿来了一个由30多页高清长图生成的pdf,在应用中运行出现了崩溃。 排查   经过调试发现加载长图生成的pdf时,运行内存会出现缓慢增长,直至崩溃。经过代码定位发现时pdf转成image对象的过程中由于是长图生成的pdf,这一页的pdf的size相当于正常pdfsize的

    2024年01月22日
    浏览(24)
  • MySQL数据库IO性能优化方法论

    作者:禅与计算机程序设计艺术 随着互联网信息化的发展,网站日益繁荣,用户对网站访问速度要求越来越高。如何提升网站数据库IO性能从而实现快速响应?本文将从数据库的优化角度出发,结合实际应用场景,进行系统地剖析、归纳和总结,为读者提供一个系统性、完整

    2024年02月06日
    浏览(39)
  • 记录一个iOS UITableView 正在刷新的时候修改数据源导致的崩溃

    首先看一下崩溃堆栈信息 由于tableview 调用layoutsubViews 执行到代理方法 -(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{ 由于是崩溃在系统方法里面的,我们无法直接看到是因为调用哪个方法导致的崩溃 后来经过多次调试,发现是因为调用了reloadData 出

    2024年01月17日
    浏览(46)
  • Cocos 微信小游戏内存与性能优化指南(iOS端)

    前言 由于微信小游戏普通模式是基于 C++ 渲染层与 JS 编译引擎在原生平台模拟 HTMLCanvas 渲染能力的方案,其中 JS 代码的编译执行效率会极大影响游戏运行的性能。通常 JS 引擎都提供了 JIT 能力用于提高编译速度,这使得小游戏能够在 Android 端取得优异的性能。但是在 IOS 端由

    2024年02月08日
    浏览(30)
  • Android性能优化系列-腾讯matrix-IO监控-IOCanaryPlugin源码分析

    作者:秋去无痕 matrix 对io的监控包括四个方面 监控在主线程执行 IO 操作的问题 监控缓冲区过小的问题 监控重复读同一文件 监控内存泄漏问题 IOCanaryPlugin,内部由IOCanaryCore完成真正的操作。 根据配置进行hook的安装 取消hook 底层hook安装包函几个步骤,加载so,设置hook内容,

    2024年02月09日
    浏览(69)
  • Kafka是如何实现高性能IO

    ​ 批量处理是一种非常有效的提升系统吞吐量的方法。在 Kafka 内部,消息都是以“批”为单位处理的。一批消息从发送端到接收端,是如何在 Kafka 中流转的呢? Kafka 的 Producer 只提供了单条发送的 send() 方法,并没有提供任何批量发送的接口。 kafka 根本就没有提供单条发送

    2024年02月11日
    浏览(34)
  • 【消息队列】Kafka如何实现高性能IO

    我们直到Kafka是一个自称高性能的消息队列引擎,一般来说对于中间件的设计需要从计算、存储、网络三方面进行下手,而消息从产生到消费,也会经历多个流程,比如在生产者端采用异步同步方式发送,采用高效的压缩算法,高效的序列化方式,以及网络IO等。那么Kafka主要

    2023年04月13日
    浏览(31)
  • 08 - 网络通信优化之IO模型:如何解决高并发下IO瓶颈?

    提到 Java I/O,相信你一定不陌生。你可能使用 I/O 操作读写文件,也可能使用它实现 Socket 的信息传输…这些都是我们在系统中最常遇到的和 I/O 有关的操作。 我们都知道,I/O 的速度要比内存速度慢,尤其是在现在这个大数据时代背景下,I/O 的性能问题更是尤为突出,I/O 读写

    2024年02月12日
    浏览(33)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包