[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;});
});

请添加图片描述

(二)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 *)originViewandTopLeft:(BOOL)topLeftandTopRight:(BOOL)topRightandBottomLeft:(BOOL)bottomLeftandBottomRight:(BOOL)bottomRightcornerRadius:(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 *)originViewcornerRadius:(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. 页面跳转的时候,取消当前页面的图片加载请求;

先来看看一般的加载逻辑,放一段我之前写过的项目的代码:
请添加图片描述

如上设置,如果我们有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:indexPathafterDelay:0.0inModes:@[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:^{//主线程刷新UIdispatch_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:^{//主线程刷新UIdispatch_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的indexpathNSArray *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。欢迎大家下载一起学习。

先写这么多吧,学习的道路还长着呢。。。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/206689.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Intellij IDEA 的安装和使用以及配置

IDE有很多种,常见的Eclipse、MyEclipse、Intellij IDEA、JBuilder、NetBeans等。但是这些IDE中目前比较火的是Intellij IDEA(以下简称IDEA),被众多Java程序员视为最好用的Java集成开发环境,今天的主题就是IDEA为开发工…

终端移动性管理

联系前面所学的知识我们知道,移动性管理主要分为两大类:空闲状态下的移动性管理、连接状态下的移动性管理。我们今天来详细了解他们的工作原理~ 目录 移动性管理分类 1、空闲状态下的移动性管理 2、连接状态下的移动性管理 手机选择天线的原则 4G天…

2023.11.29 深度学习框架理解

2023.11.29 深度学习框架理解 对深度学习框架进行复习,选最简单的“三好学生”问题的四个变化,简要总结其具体思路。 深度学习一开始就是为分类问题研究的,因此其框架的设计都是基于分类的问题,虽然现在也已经演变为可以执行多种…

SpringBoot 拦截器高级篇

Springboot 拦截器 定义使用场景拦截器与过滤器的区别实现步骤全局拦截器的局限性全局拦截器VS局部拦截器局部拦截器自定义局部拦截器使用多个局部拦截器 定义 拦截器是Spring MVC框架中的一个重要组件,它是一种AOP(面向切面编程)的实现方式&…

基于python+Django+SVM算法模型的文本情感识别系统

欢迎大家点赞、收藏、关注、评论啦 ,由于篇幅有限,只展示了部分核心代码。 文章目录 一项目简介1. 简介2. 技术栈3. 系统架构4. 关键模块介绍5. 如何运行 二、功能三、系统四. 总结 一项目简介 # 基于 Python Django SVM 算法模型的文本情感识别系统介…

kibana安装

kibana安装下载注意事项 地址:curl -O https://artifacts.elastic.co/downloads/kibana/kibana-7.16.3-linux-x86_64.tar.gz 下载后直接解压启动即可 1. 但需要使用非root用户启动 ,root用户启动会报错 2. kibana需要和elasticsearch版本一致 不然…

Blender 连续 5 天遭受大规模 DDoS 攻击

Blender 发布公告指出,在2023年11月18日至23日期间,blender.org 网站遭受了持续的分布式拒绝服务(DDoS)攻击,攻击者通过不断发送请求导致服务器超载,使网站运营严重中断。此次攻击涉及数百个 IP 地址的僵尸…

基于ASP.Net的图书管理系统的设计与实现

摘 要 图书馆管理系统是一整套高科技技术与书本管理知识结合的产物。它把传统书籍静态的服务这个缺陷完美化,完成多媒体数据的交互、远程网络连接、检查搜索智能化、多数据库无障碍联系、跨时空信息服务。图书管理系统用计算机程序替代了传统手工记录的工作模式&am…

【Openstack Train安装】二、NTP安装

网络时间协议:Network Time Protocol(NTP)是用来使计算机时间同步化的一种协议,它可以使计算机对其服务器或时钟源(如石英钟,GPS等等)做同步化,它可以提供高精准度的时间校正(LAN上与…

创建可以离线打包开发的uniapp H5项目

安装node环境 略 安装vue脚手架,在线 npm install -g vue/cli PS:vue-cli已进入维护模式,vue3最新脚手架使用npm init vuelatest安装,安装后使用create-vue替换vue指令,create-vue底层使用vite提升前端开发效率&…

ctfshow刷题web入门--1--ljcsd

文章目录 ctf.show。信息搜集web1web2web3web4web5web6web7web8web9web10web11web12web13web14web15web16web17web18web19web20。爆破。知识1.1 播种随机数生成器-mt_srand。参考web21--重点web22--做不出来web23web24web25web26web27web28。。。命令执行。知识1 绕过正则表达式…

解决Activiti5.22流程图部署在Windows上正常,但在linux上部署后出现中文变方块的问题

总结/朱季谦 楼主最近在做公司的工作流平台,发现一个很无语的事情,Activiti5.22的流程图在Windows环境上部署,是可以正常查看的,但发布到公司的Linux服务器上后,在上面进行流程图在线部署时,发现中文都变成…

大一学编程怎么学?刚接触编程怎么学习,有没有中文编程开发语言工具?

大一学编程怎么学?刚接触编程怎么学习,有没有中文编程开发语言工具? 1、大一刚开始学编程,面对复杂的代码学习非常吃力,很难入门。建议刚接触编程可以先学习中文编程,了解其中的编程逻辑,学编程…

docker启动容器失败,然后查看日志,docker logs查看容器出现报错:

docker 启动容器失败,然后docker logs 查看容器出现报错: error from daemon in stream: Error grabbing logs: invalid character l after object key:value pair在网上看到的 解决方案: 找到你日志文件目录: docker inspect …

【iOS-UIImagePickerController访问相机和相册】

【iOS-UIImagePickerController访问相机和相册】 一. UIImagePickerController的介绍1 . UIImagePickerController的作用2 . UIImagePickerController的功能 二 . UIImagePickerController的测试程序 一. UIImagePickerController的介绍 1 . UIImagePickerController的作用 U…

Rocketmq架构

NameServer:作为注册中心,提供路由注册、路由踢出、路由发现功能,舍弃强一致,保证高可用,集群中各个节点不会实时通讯,其中一个节点下线之后,会提供另外一个节点保证路由功能。 Rocket mq name…

Scrapy框架中间件(一篇文章齐全)

1、Scrapy框架初识(点击前往查阅) 2、Scrapy框架持久化存储(点击前往查阅) 3、Scrapy框架内置管道(点击前往查阅) 4、Scrapy框架中间件 Scrapy 是一个开源的、基于Python的爬虫框架,它提供了…

大一统模型 Universal Instance Perception as Object Discovery and Retrieval 论文阅读笔记

Universal Instance Perception as Object Discovery and Retrieval 论文阅读笔记 一、Abstract二、引言三、相关工作实例感知通过类别名进行检索通过语言表达式的检索通过指代标注的检索 统一的视觉模型Unified Learning ParadigmsUnified Model Architectures 四、方法4.1 Pr…

哈希思想的应用:位图、布隆过滤器及哈希切割

一.位图引入 给40定亿个不重复的无符号整数存储在文件中,如何判断一个数在不在其中? 分析:最容易想到的思路是将这些数字存储到某个能够实现快速查找的容器中,如红黑树或哈希表。 但是,10亿个字节大约占1G内存&#x…

智能优化算法应用:基于旗鱼算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用:基于旗鱼算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用:基于旗鱼算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.旗鱼算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…