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 或首次使用时再执行。
objectivec
// 不推荐:在 +load 中执行耗时操作
+ (void)load {
// 初始化 SDK、注册通知等
[SomeSDK initialize];
}
// 推荐:延迟到首次使用时初始化
+ (void)initialize {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
[SomeSDK initialize];
});
}
使用 __attribute__((constructor)) 替代部分 +load:对于 C 函数,可以使用构造函数属性,但同样要注意避免在其中执行耗时操作。
3.2 main() 到首帧渲染的优化
这一阶段主要执行 main() 函数中的初始化逻辑,包括:
- 初始化 AppDelegate
- 创建 UIWindow 和根控制器
- 加载首屏数据
优化策略:
减少首屏初始化工作量 :将非必要的初始化逻辑延迟到首帧渲染之后执行,例如使用 dispatch_async 将次要任务放到后台队列。
swift
func application(_ 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()
}
return true
}
使用启动 Storyboard 优化:iOS 14+ 支持使用 Launch Storyboard 替代静态启动图,可以减少启动时的资源加载。
3.3 首帧渲染之后的优化
首帧渲染完成后,应用进入可交互状态,但仍需关注以下优化点:
- 预加载数据:在空闲时预加载用户可能访问的数据,减少后续页面加载时间。
- 延迟注册通知:避免在启动阶段注册大量通知,减少不必要的系统调用。
- 使用启动屏优化感知:通过合理的启动屏设计,让用户感觉启动更快。
4. 内存优化
内存优化是 iOS 性能优化的核心内容之一,涉及内存泄漏、内存峰值、对象生命周期等多个方面。
4.1 内存泄漏检测与修复
内存泄漏是指对象不再被使用但依然被强引用,导致无法释放。常见的内存泄漏场景包括:
- 循环引用(Block、Delegate、NSTimer)
- 静态变量持有对象
- 通知未移除
循环引用示例与修复:
swift
// 问题代码:Block 捕获 self 导致循环引用
class ViewController: UIViewController {
var closure: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
// self -> closure -> self,形成循环引用
closure = {
self.view.backgroundColor = .white
}
}
}
// 修复:使用 weak 打破循环
class ViewController: UIViewController {
var closure: (() -> Void)?
override func viewDidLoad() {
super.viewDidLoad()
weak var weakSelf = self
closure = {
weakSelf?.view.backgroundColor = .white
}
}
}
NSTimer 循环引用:NSTimer 会强持有 target,如果 target 又持有 timer,就会形成循环引用。iOS 10+ 推荐使用基于 Block 的 Timer API:
swift
// 推荐:使用 Block 版本 Timer,配合 weak self
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { [weak self] _ in
self?.updateUI()
}
4.2 内存峰值控制
内存峰值过高会导致系统发出内存警告,甚至杀死应用。控制内存峰值的策略:
图片内存优化 :图片是内存消耗的大户。使用 UIImage 时,应避免加载超过屏幕尺寸的大图,使用 imageWithContentsOfFile: 替代 imageNamed:(后者会缓存图片)。
swift
// 按需加载,不缓存
let path = Bundle.main.path(forResource: "photo", ofType: "jpg")!
let image = UIImage(contentsOfFile: path)
// 使用缩略图
let thumbnail = image?.preparingThumbnail(of: CGSize(width: 200, height: 200))
使用自动释放池 :在循环中创建大量临时对象时,使用 autoreleasepool 及时释放内存。
swift
// 大量临时对象场景
for i in 0..<10000 {
autoreleasepool {
let tempString = 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 渲染和用户交互,任何耗时操作都不应放在主线程执行。常见的阻塞场景包括:
- 同步网络请求
- 大量数据解析
- 复杂布局计算
- 文件读写
优化示例:将耗时操作移到后台线程:
swift
// 不推荐:主线程同步加载数据
let data = try? Data(contentsOf: url)
let json = try? JSONSerialization.jsonObject(with: data!)
// 推荐:后台线程处理,主线程更新 UI
DispatchQueue.global(qos: .userInitiated).async {
let data = try? Data(contentsOf: url)
let json = try? JSONSerialization.jsonObject(with: data!)
DispatchQueue.main.async {
// 更新 UI
self.updateUI(with: json)
}
}
5.2 合理使用 GCD 和 OperationQueue
GCD 提供了高效的并发编程接口,但使用不当也会带来性能问题:
避免创建过多串行队列:每个串行队列都会占用系统资源,应尽量复用队列。
使用 QoS 合理分配优先级:
swift
// 用户交互相关任务:高优先级
DispatchQueue.global(qos: .userInteractive).async {
// 处理用户交互相关计算
}
// 非紧急任务:低优先级
DispatchQueue.global(qos: .background).async {
// 后台预加载、日志上报等
}
避免线程爆炸 :使用 DispatchSemaphore 或 OperationQueue 的 maxConcurrentOperationCount 限制并发数。
5.3 优化数据解析
数据解析是常见的 CPU 密集型操作,优化手段包括:
- 使用
Codable替代手写解析逻辑 - 避免重复解析相同数据,做好缓存
- 大数据量解析放到后台线程
swift
// 使用 Codable 高效解析
struct User: Codable {
let id: Int
let name: String
let email: String
}
let decoder = JSONDecoder()
let user = 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遮罩
优化圆角渲染:
swift
// 不推荐:触发离屏渲染
imageView.layer.cornerRadius = 10
imageView.layer.masksToBounds = true
// 推荐:使用 Core Graphics 绘制圆角图片
func roundedImage(image: UIImage, cornerRadius: CGFloat) -> UIImage {
let rect = CGRect(origin: .zero, size: image.size)
UIGraphicsBeginImageContextWithOptions(image.size, false, image.scale)
UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
image.draw(in: rect)
let result = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return result ?? image
}
6.2 合理使用图层
减少图层数量:每个 UIView 都对应一个 CALayer,过多的图层会增加渲染负担。尽量使用扁平化的视图层级。
使用 opaque 属性 :将不透明的视图设置为 opaque = true,可以减少混合计算。
swift
// 不透明视图
view.opaque = true
view.backgroundColor = .white
避免不必要的透明效果:透明视图需要与下层内容进行混合,增加 GPU 负担。
6.3 优化 Auto Layout
Auto Layout 的约束计算在复杂界面中可能成为性能瓶颈。优化策略:
- 使用
UIStackView减少约束数量 - 避免约束冲突和警告
- 在滚动视图中使用
estimatedRowHeight估算高度 - 复杂布局使用 Frame 布局或
UICollectionViewCompositionalLayout
6.4 图片渲染优化
- 使用正确尺寸的图片,避免大图缩放
- 使用
UIImageView的contentMode合理设置 - 使用
ImageIO渐进式加载大图 - 使用
UIGraphicsImageRenderer替代UIGraphicsBeginImageContext(iOS 10+)
swift
// 使用 UIGraphicsImageRenderer 高效绘制
let renderer = UIGraphicsImageRenderer(size: CGSize(width: 200, height: 200))
let image = renderer.image { context in
UIColor.blue.setFill()
context.fill(CGRect(x: 0, y: 0, width: 200, height: 200))
}
7. 网络优化
网络请求是移动应用最常见的性能瓶颈之一。网络优化可以从减少请求、压缩数据、合理缓存、连接复用等角度展开。
7.1 减少网络请求
- 合并请求:将多个小请求合并为一个批量请求
- 避免重复请求:对相同请求做去重处理
- 使用分页加载:列表数据使用分页,避免一次性加载全部数据
7.2 数据压缩
- 使用 HTTP/2 的头部压缩特性
- 对 JSON 数据使用 gzip 压缩
- 使用更高效的数据格式(如 Protobuf)替代 JSON
swift
// 配置 URLSession 支持压缩
let config = URLSessionConfiguration.default
config.httpAdditionalHeaders = ["Accept-Encoding": "gzip"]
let session = URLSession(configuration: config)
7.3 合理使用缓存
- 使用
NSURLCache缓存 GET 请求 - 使用
ETag和Last-Modified做条件请求 - 对图片等静态资源使用磁盘缓存
swift
// 配置 URLCache
let cacheSize = 50 * 1024 * 1024 // 50MB
let cache = URLCache(memoryCapacity: cacheSize, diskCapacity: cacheSize)
URLCache.shared = cache
7.4 连接复用
- 使用 HTTP/2 多路复用
- 使用
URLSession的默认连接池 - 避免频繁创建和销毁
URLSession
7.5 使用 URLSession 替代第三方库
对于简单的网络请求,使用系统自带的 URLSession 比引入 AFNetworking/Alamofire 更轻量,减少依赖和启动开销。
8. 耗电优化
耗电优化不仅影响用户体验,还影响应用在后台的存活时间。耗电的主要来源包括 CPU 高负载、网络请求、GPS 定位、后台任务等。
8.1 优化定位服务
- 使用
significantLocationChange替代高精度持续定位 - 根据场景动态调整定位精度
- 在不需要定位时及时停止更新
swift
// 使用低功耗定位
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters
locationManager.pausesLocationUpdatesAutomatically = true
8.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 支持在单元测试中编写性能测试用例:
swift
func testPerformanceExample() {
measure {
// 被测代码
self.dataProcessor.process(data: testData)
}
}
9.4 建立性能监控体系
在生产环境中,应建立完善的性能监控体系:
- 监控启动时间、FPS、内存占用等核心指标
- 使用
os_signpost记录关键路径耗时 - 上报性能数据到后台,建立基线并设置