赞
踩
APP 的性能指标主要是包括 CPU、GPU、内存、电池耗电、网络加载几个大的方面,网络加载在下文会提及,电池耗电主要是由于 CPU、GPU、网络等因素决定,所以不作为基础的指标。
iOS APP 为单进程的应用,不涉及到跨进程通讯(不包括 Extention)。
线程的使用及通讯会带来 CPU 的开销,大量的线程启用自然时候使得 CPU 使用率上升,不同线程之间的通讯需要添加锁来确保线程安全,又加大了线程的使用周期。
使用线程时需要注意:
常见较为耗时的场景如下。
- /* 避免频繁创建NSDateFormatter实例 */
- + (NSDateFormatter *)dateFormatter {
- static NSDateFormatter *formatter;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- formatter = [[NSDateFormatter alloc] init];
- formatter.dateFormat = @"yyyy-MM-dd HH:mm:ss";
- });
- return formatter;
- }
I/O 操作是指文件的读取、写入、更新。磁盘 I/O 的执行速度要远低于 CPU 和内存的速度。文件的读写主要性能开销是 I/O,同时也会有小占比的 CPU 与内存的消耗。
在 APP 运行过程中,由于 I/O 操作速度较慢,方法的调用时耗自然也就更大,通常会使用多线程来进行文件的读写操作,防止主线程的堵塞。文件大小与文件数量关系着线程资源的开销,最终决定 CPU 的性能开销。
Xcode自带的 CPU 检测工具:
第三方开源的 CPU 检测组件:
FPS :Frames Per Second 的简称缩写,意思是每秒传输帧数,可以理解为我们常说的“刷新率”(单位为Hz)。FPS 是测量用于保存、显示动态视频的信息数量。每秒钟帧数愈多,所显示的画面就会愈流畅,FPS 值越低就越卡顿,所以这个值在一定程度上可以衡量应用在图像绘制渲染处理时的性能。iOS 系统中正常的屏幕刷新率为 60Hz(60次每秒)。页面渲染优化相关内容会在下文根据具体场景列举说明。
Xcode 自带的 FPS 检测工具:
第三方开源的FPS检测组件:
这里讲的内存主要是内存缓存,不对内存管理做过多的叙述,有兴趣可以看一下我之前写的文章-iOS内存管理。
每一台 iPhone 机子都拥有固定的物理内存空间,也就是我们常说的运行内存 2个G、4个G 这种硬件配置。系统的运行会有一部分的内存开销,其他的则由运行的 APP 共同分配。
和安卓不同的是,IOS 系统并没有限制固定的内存分配规则,所以运行一个 APP,有时候可以达到几百甚至超过 1GB 的内存使用,不过这样无限制的消耗内存会导致内存警告,最终导致进程被杀掉。
内存的使用场景:
内存的缓存策略:MemoryCache
Xcode自带的内存检测工具:
第三方开源的内存监控组件:
iOS 冷启动流程分为 Pre-main 与 main,也就是 main 函数入口的之前与之后的两部分。网上这方面的资料也很多,这里就大概过一下,相关的博文推荐:抖音-iOS启动优化之原理篇、抖音-iOS启动优化之实战篇、抖音-基于二进制文件重排的解决方案
1)具体流程
2)优化策略
1)具体流程
2)优化策略
1)View的渲染
View 的展示是由 Layer 实现,View 主要处理 Touch 响应链相关的事件。UIView 提供了绘图 API-drawRect,可以在该方法中获取图形上下文,并实现图形的绘制,调用 setNeedsDisplay 刷新绘制。
当 View / Layer 的 frame 与图层结构发生改变,或者是手动调 setNeedsLayout / setNeedsDisplay方法时,View / Layer 被标记为待处理状态,系统会监听 mainRunLoop 的 BeforeWaiting / Exit 状态,在监听回调中遍历所有待处理 View / Layer,实现 UI 的刷新。View 添加 subView 也是在 mainRunloop 的回调中实现 UI 绘制,所以 View 的 layoutSubviews / drawRect 方法只有在不同 mainRunloop 的回调节点才会多次触发。
上面提到 View 的本质是 Layer,Layer 则包含 contents,这个 contents 指向的是一块缓存又名Baking Store。Objective-c 提供了 Core Animation 的渲染内核,底层是由 OpenGL 实现 GPU 渲染,流程大致如下:
所有的 Bitmap,包括图片、文本、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture。
2)GPU的离屏渲染
离屏渲染主要开销包括创建新的缓冲区、屏幕缓冲区到离屏缓冲区的来回切换。
iOS 中主要是由于 Layer 的某些属性设置导致的离屏渲染,常见的有遮罩(mask)、透明(opaque)、阴影(shadow)、光栅化(rasterize)、圆角(cornerRadius),离屏渲染会让 APP 的交互变得不流畅(如:比较复杂的图文混排 List),需要避免频繁触发离屏渲染,相关博文推荐:iOS离屏渲染场景及优化方案
原生页面的复杂布局一般有两种常见的场景:
2)低复用列表
- /* 使用异步线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片 */
- - (void)drawImage:(UIImage *)image {
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- CGImageRef imageRef = image.CGImage;
- size_t width = image.size.width;
- size_t height = image.size.height;
- size_t bitsPerComponent = CGImageGetBitsPerComponent(imageRef);
- size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
- CGColorSpaceRef space = CGImageGetColorSpace(imageRef);
- uint32_t bitmapInfo = CGImageGetBitmapInfo(imageRef);
-
- CGContextRef contextRef = CGBitmapContextCreate(NULL, width, height, bitsPerComponent, bytesPerRow, space, bitmapInfo);
- CGContextDrawImage(contextRef, CGRectMake(0, 0, width, height), imageRef);
- CGImageRef tImageRef = CGBitmapContextCreateImage(contextRef);
- CGContextRelease(contextRef);
-
- dispatch_async(dispatch_get_main_queue(), ^{
- self.layer.contents = (__bridge id)tImageRef;
- CGImageRelease(tImageRef);
- });
- });
- }
3)频繁画布重绘
- /* 异步绘制,在需要频繁重绘的视图上效果最好(比如绘图应用、TableViewCell之类)*/
- - (void)drawsAsynchronously:(void(^)(CGContextRef context))drawsBlock {
- /* 开启异步线程实现图形绘制,最终刷新还是在UI线程 */
- CGSize size = self.bounds.size;
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- UIGraphicsBeginImageContext(size);
- CGContextRef context = UIGraphicsGetCurrentContext();
- drawsBlock(context);
- UIImage *tImage = UIGraphicsGetImageFromCurrentImageContext();
- UIGraphicsEndImageContext();
-
- dispatch_async(dispatch_get_main_queue(), ^{
- self.layer.contents = (__bridge id)tImage.CGImage;
- });
- });
-
- /* 或者使用系统提供的属性来实现异步绘制
- self.layer.drawsAsynchronously
- */
- }
UI 动画往往对性能的开销比较大,iOS 项目中最常见的动画包括帧动画与核心动画。通过 imageView 配置帧图片的方式,或者git组件实现帧动画效果,
UIImageView animations 适用于帧数较少的场景,省去了 Gif 解析的环节,直接配置帧图片。
Gif 的播放对 CPU 与内存的开销较大(文件解析->缓存->定时器->解码显示),可以使用FLAnimatedImage / YYImage(本地)、SDWebImage(网络),都对 Gif 渲染做了优化。就FLAnimatedImage 的实现而言,从三个方面优化了 Gif 的渲染,分别是异步解析 gifData、CADisplayLink 的使用、gifData 大小制定缓存策略(见下方图片)。
尽管对 Gif 渲染做了一定的优化,但在 Gif 帧数及帧图片较大的时候,Gif 仍是会带来不少的开销,特别是多个 Gif 同时渲染的页面。Lottie 的出现很好地解决了这个问题,一个基于移动端和 web 端的跨平台动画框架,设计师可以使用 lottie 提供的 Bodymovin 插件将设计好的动画导出成 JSON 格式,并在移动端和Web端实现动画的渲染。
动画的冲突也会出现明显的卡顿现象,如在 Push 一个 VC 时,该 VC 页面即刻唤起键盘,就会出现卡顿或者是没有弹起动效的情况,可以通过异步调用的方式来规避。
核心动画包含基础动画、关键帧动画、组合动画、过度动画,可以直接调用系统提供的 API 实现。
1)白屏时间长
2)图片展示
1)图片加载支持webp
WebP 是一种同时提供了 有损压缩 与 无损压缩(可逆压缩)的图片文件格式,派生自影像编码格式 VP8,是由 Google 在购买 On2 Technologies 后发展出来,以 BSD 授权条款发布。
具体实现流程:
2)HttpDNS解析
HttpDNS 解析是使用 HTTP 协议进行域名解析,代替现有基于 UDP 的 DNS 协议,域名解析请求直接发送到阿里云的 HTTPDNS 服务器,从而绕过运营商的 Local DNS,能够避免 Local DNS 造成的域名劫持问题和调度不精准问题。
httpDns 解析将现有域名解析成 IP 地址,通过 IP 直连的方式进行网络访问。市面上的 APP 大部分是通过的阿里云与腾讯云提供的 SDK 来实现。
移动解析HttpDNS_移动互联网域名解析_域名防劫持 - 腾讯云
具体实现流程:
3)使用网络缓存 + 请求数据压缩 + 接口分屏加载
在项目经过长周期的迭代后,Run / Archive 的时长从一开始的几分钟到十几二十几分钟,一方面由于 Mac 设备更新换代,另一方面则是工程架构的复杂化,或者是项目设计不合理导致的臃肿。
原生业务比较多的 APP,在经过一定迭代后,ipa 包都会比较大,上百兆、甚至达到了一两百兆。这时候就需要优化包大小,相关的博文推荐:今日头条 iOS 安装包大小优化
附件 & 代码: iOS性能优化-扩展图.xmind、MemoryCache、APM-简单功能的实现
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。