iOS性能优化 - 分析&应用

内存的使用场景:

  • 临时/局部,临时申请的内存空间,使用完即释放,如二级页面的数据源缓存。
  • 静态/全局,静态内存,static、const、extern 声明的常量与对象(单例对象、全局数组)。

内存的缓存策略:MemoryCache

  • 常规缓存,NSDictionary、NSArray、NSSet、NSPointerArray / NSMapTable / NSHashTable(支持弱引用)。
  • 缓存+淘汰策略,LRU、LFU、NSCache(LFU 优先于 LRU)。

Xcode自带的内存检测工具:

第三方开源的内存监控组件:

  • Faceboo k的 FBMemoryProfiler,分析 iOS 内存使用和检测循环引用,仅检测 OC。
  • 腾讯的 OOMDetector,OOM 监控、大内存分配监控、内存泄漏检测,支持监控 C++ 对象和 malloc 内存块以及 VM 内存。

二. 场景应用

1. 启动

iOS 冷启动流程分为 Pre-main 与 main,也就是 main 函数入口的之前与之后的两部分。网上这方面的资料也很多,这里就大概过一下,相关的博文推荐:抖音-iOS启动优化之原理篇抖音-iOS启动优化之实战篇抖音-基于二进制文件重排的解决方案

1.1 Pre-main

1)具体流程

  • Dyld:动态链接器,在系统内核做好程序准备工作之后,交由 dyld 负责余下的工作。
  • Load Dylibs:加载动态库,iOS 的动态库包含 dylib 与动态 framework,静态库包含 .a 与静态 framework。
  • Rebase:将镜像读入内存,修正镜像内部的指针,并以 Page 为单位进行签名验证,保证不会被篡改,性能消耗主要在 I/O。
  • Bind:查询符号表,设置指向镜像外部的指针,性能消耗主要在 CPU 计算。
  • Objc:读取所有类,将类对象其注册到这个全局表中;读取所有分类,把分类加载到类对象中; 检查 selector 的唯一性。
  • initalizers:dyld 开始运行程序的初始化函数,调用每个 Objc 类和分类的 +load 方法,调用C/C++ 中的构造器函数,和创建非基本类型的C++静态全局变量。

2)优化策略

  • 合并自定义的动态库,删除冗余的 dylib 与动态 framework。
  • 将动态库转为静态库(Mach-O Type:Static Library)。
  • 使用二进制重排,减少 Page 载入的缺页中断问题。
  • 减少 ObjC 类、方法(selector)、类别(category)的数量。
  • 减少 ObjC 的 +load 方法,类/协议的绑定通过启动项自注册的方式实现。
  • 减少C的 constructor 函数、C++ 静态对象。
1.2 Main

1)具体流程

2)优化策略

  • SDK 注册较为耗时的可以使用异步并发加载,部分二级页才用到的 SDK 可以采用懒加载的形式。
  • 防止启动时有过多的串行接口操作,尽量精简。
  • 避免启动后出现过多的耗性能操作,例如频繁读写 I/O,数据解码等耗时方法的调用。

2. 页面

2.1 原生页面-渲染原理

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 渲染,流程大致如下:

  1. 初始化用于绘制的上下文 EAGLContext;
  2. 创建帧缓冲区和渲染缓冲区,设置画布的宽高;
  3. 添加附件,比如颜色附件或者深度附件;
  4. 切换到帧缓冲区,在帧缓冲中进行绘制;
  5. 切换到屏幕缓冲区,读取帧缓冲中的信息;
  6. 绘制到屏幕上,在容器 dealloc 时删除缓冲区。

所有的 Bitmap,包括图片、文本、栅格化的内容,最终都要由内存提交到显存,绑定为 GPU Texture。

2)GPU的离屏渲染

  • 当前屏幕渲染,指的是 GPU 的渲染操作在当前用于显示的屏幕缓冲区进行。
  • 离屏渲染,指的是 GPU 在当前屏幕缓冲区外新开辟一个缓冲区进行渲染操作。

离屏渲染主要开销包括创建新的缓冲区、屏幕缓冲区到离屏缓冲区的来回切换。

iOS 中主要是由于 Layer 的某些属性设置导致的离屏渲染,常见的有遮罩(mask)、透明(opaque)、阴影(shadow)、光栅化(rasterize)、圆角(cornerRadius),离屏渲染会让 APP 的交互变得不流畅(如:比较复杂的图文混排 List),需要避免频繁触发离屏渲染,相关博文推荐:iOS离屏渲染场景及优化方案

2.2 原生页面-复杂布局

原生页面的复杂布局一般有两种常见的场景:

  • 微博、空间、朋友圈这些样式多样化的列表页,呈现的特点是 Cell 的复用程度较低,单元图层元素也较为复杂。
  • 股票K线图,图像编辑、动态图表等图形绘制页面,主要特点是在一个固定的画布中,根据数据源与对应的场景需求,快速地绘制并展示图形。

2)低复用列表

  • 离屏渲染:mask/opaque/shadow/rasterize/cornerRadius,这些属性都会引起离屏渲染,低刷新频率并不会出现卡帧的现象,主要出现在列表页快速滑动的时候。
  • 视图过度绘制:Cell 中的非交互图层使用 Layer 代替;减少图层嵌套,精简图层的数量;使用异步渲染,开启子线程把复杂的图层元素绘制在 ImageContext,获取 Image 后切回主线程展示。
  • 数据加载:通过懒加载 / 预加载,具体场景具体应用;使用异步线程实现数据的获取 / 加工(I/O操作、数据换算)。
  • 图片预解码:把图片解码从主线程切换到子线程,使用异步线程先把图片绘制到 CGBitmapContext 中,然后从 Bitmap 直接创建图片。
/* 使用异步线程先把图片绘制到 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)频繁画布重绘

  • 统一事件源触发:定时器与 Touch 事件源的统一入口,避免事件源过于频繁的触发图层重绘。
  • 减少局部刷新:以整体数据源的变化为刷新频次,减少局部刷新的频率(与统一事件源类似)。
  • 减少图层嵌套:图形绘制的场景减少 Layer 以及图层嵌套,使用效率更优的 CGGraphis-API。
  • 使用异步绘制:开启子线程把复杂的图层元素绘制在 ImageContext,获取 Image 后切回主线程展示。
/* 异步绘制,在需要频繁重绘的视图上效果最好(比如绘图应用、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
     */
}
2.2 原生页面-动画效果

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实现。

2.3 web页面

1)白屏时间长

  • **资源本地化:**web 页面常见的问题就是白屏时间长,需要依次加载 html,cdn 资源文件,以及页面的网络请求。可以通过加载 H5 本地资源包的方式,或者 cdn 资源拦截+本地映射的方式来减少白屏时长,具体实现可以参考H5资源本地化策略-iOS
  • **骨架屏:**尽管页面加载网络数据时会有加载圈提示,但接口响应较慢会导致页面一直在转圈,这时就需要引入骨架屏,页面在加载完 web 资源后,通过 webpage 打包生成的骨架屏预先展示出页面的大致结构 (Vue页面骨架屏注入实践),或者是通过设置各个 UI 组件的占位来预先展示出页面的大致结构。

2)图片展示

  • **上传压缩:**减少网路加载时耗,以及大图片的渲染开销。
  • **图片占位:**防止图片加载时,页面出现跳动的现象。
2.4 网络加速

1)图片加载支持webp

WebP 是一种同时提供了 有损压缩 与 无损压缩(可逆压缩)的图片文件格式,派生自影像编码格式 VP8,是由 Google 在购买 On2 Technologies 后发展出来,以 BSD 授权条款发布。

具体实现流程:

  1. 服务端支持图片的 webp 加载;
  2. 通过 Hook 文件下载 API,给图片 url 添加后缀 ‘.webp’;
  3. 加载 webp 资源文件;
  4. SDWebImage 自带 webp 解码器,APP 启动时注册一下即可;
  5. webp 解码成 jpg/png,图片展示。

2)HttpDNS解析

HttpDNS 解析是使用 HTTP 协议进行域名解析,代替现有基于 UDP 的 DNS 协议,域名解析请求直接发送到阿里云的 HTTPDNS 服务器,从而绕过运营商的 Local DNS,能够避免 Local DNS 造成的域名劫持问题和调度不精准问题。

httpDns 解析将现有域名解析成 IP 地址,通过 IP 直连的方式进行网络访问。市面上的 APP 大部分是通过的阿里云与腾讯云提供的 SDK 来实现。

HTTPDNS_域名解析_域名防劫持_开发与运维-阿里云

移动解析HttpDNS_移动互联网域名解析_域名防劫持 - 腾讯云

具体实现流程:

  1. 通过 NSURLProtocol 对请求进行重定向;
  2. 获取域名解析后的IP信息;

总结

作为一名从事Android的开发者,很多人最近都在和我吐槽Android是不是快要凉了?而在我看来这正是市场成熟的表现,所有的市场都是温水煮青蛙,永远会淘汰掉不愿意学习改变,安于现状的那批人,希望所有的人能在大浪淘沙中留下来,因为对于市场的逐渐成熟,平凡并不是我们唯一的答案!

资料.png
资料图.jpg

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

[外链图片转存中…(img-TWedrRed-1714502718329)]
[外链图片转存中…(img-1OSkv8Qz-1714502718330)]

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化学习资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 10
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值