1. 引言
在移动应用开发中,性能优化是决定产品成败的关键因素之一。一个流畅、响应迅速的应用不仅能提升用户体验,还能显著提高用户留存率和应用商店评分。对于 iOS 开发者而言,性能优化涉及 CPU、内存、GPU、网络、启动时间等多个维度,是一项系统性的工程。
本文将从 iOS 性能优化的核心指标出发,系统性地讲解启动优化、内存优化、CPU 优化、渲染优化、网络优化、耗电优化等关键方向,并结合实际代码示例,帮助开发者建立完整的性能优化知识体系。
2. 性能优化的核心指标
在开始优化之前,我们需要先明确衡量性能的关键指标。iOS 应用的性能可以从以下几个维度进行评估:
2.1 启动时间
启动时间是用户感知最直接的性能指标,分为冷启动和热启动:
- 冷启动:应用进程从无到有,系统加载应用二进制、动态库并执行 main 函数的过程。
- 热启动:应用已在后台,用户重新唤起时的过程,通常较快。
业界普遍认为,冷启动时间应控制在 2 秒以内,超过 2 秒用户流失率会显著上升。
2.2 FPS(帧率)
FPS(Frames Per Second)表示每秒渲染的帧数。iOS 设备的屏幕刷新率为 60Hz 或 120Hz(ProMotion),理想情况下 FPS 应接近屏幕刷新率。当 FPS 低于 45 时,用户会明显感觉到卡顿。
2.3 CPU 占用率
CPU 占用率反映了应用对处理器资源的消耗。过高的 CPU 占用会导致设备发热、耗电加快,并可能触发系统的降频机制。在正常使用场景下,CPU 占用率应控制在较低水平,避免长时间高负载运行。
2.4 内存占用
内存占用直接影响应用的稳定性和系统对应用的管理策略。iOS 系统在内存压力较大时会发出内存警告,甚至直接杀死应用(Jetsam)。开发者需要关注内存峰值和泄漏问题。
2.5 网络请求耗时
网络请求耗时影响页面加载速度和用户体验。优化网络层可以减少请求次数、压缩数据体积、利用缓存机制,从而降低整体耗时。
3. 启动优化
启动优化是性能优化中最直观、收益最明显的方向。启动过程可以划分为三个阶段:main()函数之前、main()函数到首帧渲染、首帧渲染之后。
3.1 main() 之前的优化
在main()函数执行之前,系统会完成以下工作:
- 加载应用可执行文件(Mach-O)
- 加载动态库(dylib)
- 执行
+load方法和静态初始化
针对这一阶段的优化手段包括:
减少动态库数量:每多一个动态库,启动时就需要多一次加载和链接操作。尽量将不必要的功能静态链接或合并到主工程中。
清理+load方法:+load方法在应用启动时同步执行,过多的+load方法会显著拖慢启动速度。应将其中的逻辑迁移到+initialize或首次使用时再执行。
// 不推荐:在 +load 中执行耗时操作+(void)load{// 初始化 SDK、注册通知等[SomeSDK initialize];}// 推荐:延迟到首次使用时初始化+(void)initialize{staticdispatch_once_t onceToken;dispatch_once(&onceToken,^{[SomeSDK initialize];});}使用__attribute__((constructor))替代部分+load:对于 C 函数,可以使用构造函数属性,但同样要注意避免在其中执行耗时操作。
3.2 main() 到首帧渲染的优化
这一阶段主要执行main()函数中的初始化逻辑,包括:
- 初始化 AppDelegate
- 创建 UIWindow 和根控制器
- 加载首屏数据
优化策略:
减少首屏初始化工作量:将非必要的初始化逻辑延迟到首帧渲染之后执行,例如使用dispatch_async将次要任务放到后台队列。
funcapplication(_application:UIApplication,didFinishLaunchingWithOptions launchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{// 首屏必需初始化window=UIWindow(frame:UIScreen.main.bounds)window?.rootViewController=MainViewController()window?.makeKeyAndVisible()// 非关键初始化延迟执行DispatchQueue.global(qos:.utility).async{self.setupThirdPartySDKs()self.preloadData()}returntrue}使用启动 Storyboard 优化:iOS 14+ 支持使用 Launch Storyboard 替代静态启动图,可以减少启动时的资源加载。
3.3 首帧渲染之后的优化
首帧渲染完成后,应用进入可交互状态,但仍需关注以下优化点:
- 预加载数据:在空闲时预加载用户可能访问的数据,减少后续页面加载时间。
- 延迟注册通知:避免在启动阶段注册大量通知,减少不必要的系统调用。
- 使用启动屏优化感知:通过合理的启动屏设计,让用户感觉启动更快。
4. 内存优化
内存优化是 iOS 性能优化的核心内容之一,涉及内存泄漏、内存峰值、对象生命周期等多个方面。
4.1 内存泄漏检测与修复
内存泄漏是指对象不再被使用但依然被强引用,导致无法释放。常见的内存泄漏场景包括:
- 循环引用(Block、Delegate、NSTimer)
- 静态变量持有对象
- 通知未移除
循环引用示例与修复:
// 问题代码:Block 捕获 self 导致循环引用classViewController:UIViewController{varclosure:(()->Void)?overridefuncviewDidLoad(){super.viewDidLoad()// self -> closure -> self,形成循环引用closure={self.view.backgroundColor=.white}}}// 修复:使用 weak 打破循环classViewController:UIViewController{varclosure:(()->Void)?overridefuncviewDidLoad(){super.viewDidLoad()weakvarweakSelf=selfclosure={weakSelf?.view.backgroundColor=.white}}}NSTimer 循环引用:NSTimer 会强持有 target,如果 target 又持有 timer,就会形成循环引用。iOS 10+ 推荐使用基于 Block 的 Timer API:
// 推荐:使用 Block 版本 Timer,配合 weak selftimer=Timer.scheduledTimer(withTimeInterval:1.0,repeats:true){[weakself]_inself?.updateUI()}4.2 内存峰值控制
内存峰值过高会导致系统发出内存警告,甚至杀死应用。控制内存峰值的策略:
图片内存优化:图片是内存消耗的大户。使用UIImage时,应避免加载超过屏幕尺寸的大图,使用imageWithContentsOfFile:替代imageNamed:(后者会缓存图片)。
// 按需加载,不缓存letpath=Bundle.main.path(forResource:"photo",ofType:"jpg")!letimage=UIImage(contentsOfFile:path)// 使用缩略图letthumbnail=image?.preparingThumbnail(of:CGSize(width:200,height:200))使用自动释放池:在循环中创建大量临时对象时,使用autoreleasepool及时释放内存。
// 大量临时对象场景foriin0..<10000{autoreleasepool{lettempString=String(format:"Item %d",i)// 处理 tempString}}避免不必要的缓存:合理使用 NSCache 替代 NSDictionary 做缓存,NSCache 在内存紧张时会自动清理。
4.3 使用 Instruments 检测内存问题
Xcode 自带的 Instruments 工具提供了强大的内存分析能力:
- Leaks:检测内存泄漏
- Allocations:分析内存分配情况,定位内存峰值
- Zombies:检测野指针访问
使用 Leaks 工具定位泄漏的典型流程:
- 使用 Xcode 的 Profile 模式运行应用
- 选择 Leaks 模板
- 操作应用触发可疑场景
- 查看泄漏对象及其调用栈
5. CPU 优化
CPU 占用过高会导致设备发热、耗电加快和界面卡顿。CPU 优化的核心是减少不必要的计算和合理利用多核能力。
5.1 避免主线程阻塞
主线程负责 UI 渲染和用户交互,任何耗时操作都不应放在主线程执行。常见的阻塞场景包括:
- 同步网络请求
- 大量数据解析
- 复杂布局计算
- 文件读写
优化示例:将耗时操作移到后台线程:
// 不推荐:主线程同步加载数据letdata=try?Data(contentsOf:url)letjson=try?JSONSerialization.jsonObject(with:data!)// 推荐:后台线程处理,主线程更新 UIDispatchQueue.global(qos:.userInitiated).async{letdata=try?Data(contentsOf:url)letjson=try?JSONSerialization.jsonObject(with:data!)DispatchQueue.main.async{// 更新 UIself.updateUI(with:json)}}5.2 合理使用 GCD 和 OperationQueue
GCD 提供了高效的并发编程接口,但使用不当也会带来性能问题:
避免创建过多串行队列:每个串行队列都会占用系统资源,应尽量复用队列。
使用 QoS 合理分配优先级:
// 用户交互相关任务:高优先级DispatchQueue.global(qos:.userInteractive).async{// 处理用户交互相关计算}// 非紧急任务:低优先级DispatchQueue.global(qos:.background).async{// 后台预加载、日志上报等}避免线程爆炸:使用DispatchSemaphore或OperationQueue的maxConcurrentOperationCount限制并发数。
5.3 优化数据解析
数据解析是常见的 CPU 密集型操作,优化手段包括:
- 使用
Codable替代手写解析逻辑 - 避免重复解析相同数据,做好缓存
- 大数据量解析放到后台线程
// 使用 Codable 高效解析structUser:Codable{letid:Intletname:Stringletemail:String}letdecoder=JSONDecoder()letuser=try?decoder.decode(User.self,from:jsonData)5.4 避免不必要的对象创建
频繁创建和销毁对象会增加 CPU 负担和内存压力。优化策略:
- 复用 UITableViewCell / UICollectionViewCell
- 使用值类型(struct)替代部分引用类型(class)
- 避免在循环中创建不必要的对象
6. 渲染优化
渲染性能直接影响用户的视觉体验。iOS 使用 Core Animation 进行视图渲染,优化渲染性能需要从减少离屏渲染、合理使用图层、优化布局等角度入手。
6.1 避免离屏渲染
离屏渲染(Offscreen Rendering)是指 GPU 在当前屏幕缓冲区之外创建新的渲染缓冲区进行渲染,会带来额外的性能开销。常见的触发离屏渲染的操作:
- 设置圆角 +
masksToBounds - 设置阴影
- 使用
shouldRasterize - 使用
mask遮罩
优化圆角渲染:
// 不推荐:触发离屏渲染imageView.layer.cornerRadius=10imageView.layer.masksToBounds=true// 推荐:使用 Core Graphics 绘制圆角图片funcroundedImage(image:UIImage,cornerRadius:CGFloat)->UIImage{letrect=CGRect(origin:.zero,size:image.size)UIGraphicsBeginImageContextWithOptions(image.size,false,image.scale)UIBezierPath(roundedRect:rect,cornerRadius:cornerRadius).addClip()image.draw(in:rect)letresult=UIGraphicsGetImageFromCurrentImageContext()UIGraphicsEndImageContext()returnresult??image}6.2 合理使用图层
减少图层数量:每个 UIView 都对应一个 CALayer,过多的图层会增加渲染负担。尽量使用扁平化的视图层级。
使用opaque属性:将不透明的视图设置为opaque = true,可以减少混合计算。
// 不透明视图view.opaque=trueview.backgroundColor=.white避免不必要的透明效果:透明视图需要与下层内容进行混合,增加 GPU 负担。
6.3 优化 Auto Layout
Auto Layout 的约束计算在复杂界面中可能成为性能瓶颈。优化策略:
- 使用
UIStackView减少约束数量 - 避免约束冲突和警告
- 在滚动视图中使用
estimatedRowHeight估算高度 - 复杂布局使用 Frame 布局或
UICollectionViewCompositionalLayout
6.4 图片渲染优化
- 使用正确尺寸的图片,避免大图缩放
- 使用
UIImageView的contentMode合理设置 - 使用
ImageIO渐进式加载大图 - 使用
UIGraphicsImageRenderer替代UIGraphicsBeginImageContext(iOS 10+)
// 使用 UIGraphicsImageRenderer 高效绘制letrenderer=UIGraphicsImageRenderer(size:CGSize(width:200,height:200))letimage=renderer.image{contextinUIColor.blue.setFill()context.fill(CGRect(x:0,y:0,width:200,height:200))}7. 网络优化
网络请求是移动应用最常见的性能瓶颈之一。网络优化可以从减少请求、压缩数据、合理缓存、连接复用等角度展开。
7.1 减少网络请求
- 合并请求:将多个小请求合并为一个批量请求
- 避免重复请求:对相同请求做去重处理
- 使用分页加载:列表数据使用分页,避免一次性加载全部数据
7.2 数据压缩
- 使用 HTTP/2 的头部压缩特性
- 对 JSON 数据使用 gzip 压缩
- 使用更高效的数据格式(如 Protobuf)替代 JSON
// 配置 URLSession 支持压缩letconfig=URLSessionConfiguration.defaultconfig.httpAdditionalHeaders=["Accept-Encoding":"gzip"]letsession=URLSession(configuration:config)7.3 合理使用缓存
- 使用
NSURLCache缓存 GET 请求 - 使用
ETag和Last-Modified做条件请求 - 对图片等静态资源使用磁盘缓存
// 配置 URLCacheletcacheSize=50*1024*1024// 50MBletcache=URLCache(memoryCapacity:cacheSize,diskCapacity:cacheSize)URLCache.shared=cache7.4 连接复用
- 使用 HTTP/2 多路复用
- 使用
URLSession的默认连接池 - 避免频繁创建和销毁
URLSession
7.5 使用 URLSession 替代第三方库
对于简单的网络请求,使用系统自带的URLSession比引入 AFNetworking/Alamofire 更轻量,减少依赖和启动开销。
8. 耗电优化
耗电优化不仅影响用户体验,还影响应用在后台的存活时间。耗电的主要来源包括 CPU 高负载、网络请求、GPS 定位、后台任务等。
8.1 优化定位服务
- 使用
significantLocationChange替代高精度持续定位 - 根据场景动态调整定位精度
- 在不需要定位时及时停止更新
// 使用低功耗定位locationManager.desiredAccuracy=kCLLocationAccuracyHundredMeterslocationManager.pausesLocationUpdatesAutomatically=true8.2 优化后台任务
- 使用
beginBackgroundTask时设置合理的过期时间 - 避免在后台执行长时间高负载任务
- 使用
BGAppRefreshTask和BGProcessingTask替代旧的后台机制
8.3 优化网络请求
- 合并网络请求,减少无线模块的唤醒次数
- 使用推送通知替代轮询
- 在 WiFi 环境下预加载数据
8.4 优化 CPU 使用
- 避免空转循环和忙等
- 使用
DispatchSourceTimer替代NSTimer处理高频任务 - 合理使用
NSProcessInfo的beginActivity声明活动状态
9. 性能优化工具与实践
9.1 Instruments 工具集
Instruments 是 Xcode 内置的性能分析工具,包含多种模板:
| 模板 | 用途 |
|---|---|
| Time Profiler | 分析 CPU 时间分配,定位耗时方法 |
| Leaks | 检测内存泄漏 |
| Allocations | 分析内存分配与释放 |
| Core Animation | 检测渲染性能,查看 FPS |
| Network | 分析网络请求耗时与流量 |
| Energy Log | 分析耗电情况 |
9.2 使用 Time Profiler 定位 CPU 热点
- 使用 Profile 模式运行应用
- 选择 Time Profiler 模板
- 操作应用复现性能问题
- 查看调用树,定位耗时方法
9.3 使用 XCTest 进行性能测试
Xcode 支持在单元测试中编写性能测试用例:
functestPerformanceExample(){measure{// 被测代码self.dataProcessor.process(data:testData)}}9.4 建立性能监控体系
在生产环境中,应建立完善的性能监控体系:
- 监控启动时间、FPS、内存占用等核心指标
- 使用
os_signpost记录关键路径耗时 - 上报性能数据到后台,建立基线并设置