一文掌握iOS应用性能优化:内存、启动、渲染全方位解析
一文掌握iOS应用性能优化:内存、启动、渲染全方位解析
iOS应用的性能优化是一个系统性工程,涉及内存管理、启动速度、页面渲染等多个方面。本文将从这些维度出发,分享一系列实用的优化策略,帮助开发者打造更流畅、更高效的iOS应用。
一、内存管理与缓存策略
内存使用场景
- 临时/局部:如二级页面的数据源缓存,使用完即释放。
- 静态/全局:包括用static、const、extern声明的常量与对象(单例对象、全局数组)。
内存缓存策略
- 常规缓存:NSDictionary、NSArray、NSSet、NSPointerArray / NSMapTable / NSHashTable(支持弱引用)。
- 缓存+淘汰策略:LRU、LFU、NSCache(LFU 优先于 LRU)。
Xcode自带的内存检测工具
- instruments-Allocations:查内存分配情况。
- instruments-Leaks:动态内存分析。
- Xcode-Product-Analyze:静态内存分析。
第三方开源的内存监控组件
- Facebook的FBMemoryProfiler:分析内存使用和检测循环引用,仅检测OC。
- 腾讯的OOMDetector:监控OOM、大内存分配、内存泄漏,支持C++对象和malloc内存块。
二、场景应用
1. 启动优化
iOS冷启动分为Pre-main与main两部分。相关资料推荐:抖音-iOS启动优化系列文章。
1.1 Pre-main
具体流程:
- Dyld:动态链接器,负责余下工作。
- Load Dylibs:加载动态库(dylib与动态framework)。
- Rebase:将镜像读入内存,修正指针,以Page为单位进行签名验证。
- Bind:查询符号表,设置指向镜像外部的指针。
- Objc:读取所有类与分类,检查selector的唯一性。
- initalizers:运行程序的初始化函数。
优化策略:
- 合并自定义动态库,删除冗余的dylib。
- 将动态库转为静态库。
- 使用二进制重排。
- 减少ObjC类、方法数量。
- 减少+load方法调用。
- 减少C的constructor函数、C++静态对象。
1.2 Main
具体流程:略
优化策略:
- SDK注册使用异步并发加载。
- 避免过多串行接口操作。
- 避免启动后出现过多耗性能操作。
2. 页面渲染优化
2.1 原生页面-渲染原理
View的渲染:
View的展示由Layer实现,View主要处理Touch响应链相关的事件。当View/Layer的frame与图层结构发生改变,系统会在mainRunLoop的回调中遍历所有待处理View/Layer,实现UI的刷新。
底层渲染流程大致如下:
- 初始化用于绘制的上下文EAGLContext;
- 创建帧缓冲区和渲染缓冲区;
- 添加附件;
- 切换到帧缓冲区进行绘制;
- 切换到屏幕缓冲区读取信息;
- 绘制到屏幕上。
所有Bitmap最终都要由内存提交到显存,绑定为GPU Texture。
GPU的离屏渲染:
- 当前屏幕渲染:在当前屏幕缓冲区进行。
- 离屏渲染:在额外开辟的缓冲区进行。
离屏渲染主要开销包括创建新的缓冲区、屏幕缓冲区到离屏缓冲区的来回切换。iOS中主要是由于Layer的某些属性设置导致的离屏渲染,如mask、opaque、shadow、rasterize、cornerRadius。
2.2 原生页面-复杂布局
原生页面的复杂布局常见场景:
- 微博、朋友圈等样式多样化的列表页。
- 股票K线图等图形绘制页面。
低复用列表优化:
- 避免离屏渲染:注意mask、opaque、shadow、rasterize、cornerRadius属性。
- 减少视图过度绘制:Cell中的非交互图层使用Layer代替;减少图层嵌套。
- 数据加载:使用懒加载/预加载;使用异步线程实现数据的获取/加工。
- 图片预解码:把图片解码从主线程切换到子线程。
- (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);
});
});
}
频繁画布重绘优化:
- 统一事件源触发:使用定时器与Touch事件源的统一入口。
- 减少局部刷新:以整体数据源的变化为刷新频次。
- 减少图层嵌套:使用效率更优的CGGraphis-API。
- 使用异步绘制:在子线程绘制复杂的图层元素。
- (void)drawsAsynchronously:(void(^)(CGContextRef context))drawsBlock {
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;
});
});
}
2.3 原生页面-动画效果
UI动画往往对性能的开销比较大,iOS项目中最常见的动画包括帧动画与核心动画。
- UIImageView animations适用于帧数较少的场景。
- Gif的播放对CPU与内存的开销较大,可以使用FLAnimatedImage/YYImage(本地)、SDWebImage(网络)。
- Lottie是一个基于移动端和web端的跨平台动画框架,可以将设计好的动画导出成JSON格式,并在移动端和Web端实现动画的渲染。
动画的冲突也会出现明显的卡顿现象,可以通过异步调用的方式来规避。
核心动画包含基础动画、关键帧动画、组合动画、过度动画,可以直接调用系统提供的API实现。
2.4 Web页面优化
白屏时间长:
- 资源本地化:通过加载H5本地资源包或cdn资源拦截+本地映射。
- 骨架屏:页面加载网络数据时展示页面大致结构。
图片展示:
- 上传压缩:减少网路加载时耗。
- 图片占位:防止页面跳动。
2.5 网络加速
图片加载支持WebP:
WebP是一种同时提供了有损压缩与无损压缩的图片文件格式,派生自影像编码格式VP8,由Google开发。
具体实现流程:
- 服务端支持图片的webp加载;
- 通过Hook文件下载API,给图片url添加后缀‘.webp’;
- 加载webp资源文件;
- SDWebImage自带webp解码器;
- webp解码成jpg/png,图片展示。
HttpDNS解析:
HttpDNS解析是使用HTTP协议进行域名解析,代替现有基于UDP的DNS协议,能够避免Local DNS造成的域名劫持问题和调度不精准问题。
具体实现流程:
- 通过NSURLProtocol对请求进行重定向;
- 获取域名解析后的IP信息。