前言
有些 Swift 性能优化的细节,苹果文档里其实都有,但很多人可能没仔细看过。
我平时会记录一些我踩过的坑,今天整理一下分享出来,希望能帮你少走弯路。
Copy-on-Write:数组复制的隐藏成本
Swift 的 Array、Dictionary、Set 用了 Copy-on-Write(COW)机制。赋值时不会立刻复制,只有修改时才真正复制。
var arr1 = [1, 2, 3]var arr2 = arr1 // 还没复制,共享内存arr2.append(4) // 现在才复制
但如果你在循环里频繁修改大数组,还是会触发多次复制。
我之前解析 JSON,1000 条数据花了 2 秒。后来加上 reserveCapacity(1000),直接降到 0.3 秒。
知道数组大概多大时,提前用 reserveCapacity() 预留空间,能避免多次重新分配内存。
值类型 vs 引用类型:性能差很多倍
Swift 优先用值类型(struct、enum),而不是引用类型(class)。
值类型存在栈上,复制快。引用类型在堆上,还要走 ARC,开销大。
struct Point {var x: Doublevar y: Double}// 值类型复制,很快var p1 = Point(x: 1, y: 2)var p2 = p1 // 栈上复制,成本低
在游戏引擎、音频处理这些场景,用 struct 代替 class 能带来明显的性能提升。
除非需要继承或共享可变状态,否则优先用 struct。
ARC 开销可能是看不见的性能杀手
ARC 每次赋值、传参都会触发 retain/release。普通代码里这点开销可以忽略,但在循环里会累积起来。
我之前有个列表页面,滚动时卡顿。用 Instruments 一测,发现是 ARC 开销太大。把数据模型从 class 改成 struct,流畅度立马提升了。
用 struct 代替 class,值类型不走 ARC。闭包里用 [weak self] 避免强引用:
networkRequest { [weak self] data inself?.handle(data)}
函数内联可能让编译器帮你优化
函数调用有开销,编译器会在合适的时候内联函数。
静态分发 vs 动态分发:静态分发是编译器知道调用哪个函数,快。动态分发是运行时才决定,慢。
final class Dog {func bark() { print("Woof!") } // 静态分发}
类不会被继承时加 final,让编译器用静态分发。这个优化在性能关键路径上还是挺有用的。
懒加载:别让高阶函数拖慢你
map、filter、reduce 写起来爽,但可能会创建中间数组。
// 效率低,会创建临时数组let squared = (0..<1000).map { $0 * $0 }.filter { $0 % 2 == 0 }
用 lazy 可以延迟计算,避免创建中间数组。但最直接的方式还是用 for-in 循环。大数据量时用 lazy,性能关键路径用 for-in。
内存对齐:结构体的隐藏陷阱
Swift 会对齐数据,如果结构体里混着大小不同的类型,会插入填充字节,浪费内存。
// 不好的布局,浪费内存struct Bad {var a: Int8// 1 字节var b: Int64// 8 字节var c: Int8// 1 字节// Swift 会在 a 后面插入 7 字节的填充}
这个优化在普通 App 里可能不明显,但在游戏引擎、图形处理这些场景,能带来明显的性能提升。设计性能关键的结构体时,把相同大小的属性放一起。
并发优化可能让异步代码飞起来
Swift Concurrency(async/await、TaskGroup、Actors)让并发编程更安全,但也有开销。任务切换、同步、Actor 隔离,这些都是成本。
Actor 能保护共享状态,但如果太多任务同时访问同一个 Actor,会形成瓶颈。避免"热 Actor",可以分片(sharding)或者批量更新。
编译器优化,让代码跑得更快
Swift 编译器有很多优化选项:Whole Module Optimization (WMO) 跨文件优化,Release 模式默认开启。-Osize 优化二进制体积,-Ofast 激进优化(可能违反 IEEE 数学规则)。
性能测试一定要用 Release 模式,Debug 模式的性能不具参考价值。我之前在 Debug 模式下测性能,结果完全不对,后来才发现问题。
用 Instruments 找出性能瓶颈
优化之前先测量,Instruments 是 iOS 开发者的必备工具。Time Profiler 找出耗 CPU 的函数,Allocations 追踪内存使用和 ARC 开销,Leaks 发现循环引用,Energy Log 找出耗电的代码。
我之前有个 SwiftUI 列表,滚动时卡顿。用 Time Profiler 一测,发现是在主线程解析 JSON。移到后台 Task 里,立马流畅了。这种问题其实挺常见的,但很多人可能没意识到。
优化前后都要测,你永远不知道瓶颈在哪里。
实战场景:JSON 解析、Core Data、SwiftUI
JSON 解析:
let decoder = JSONDecoder()decoder.keyDecodingStrategy = .useDefaultKeys // 避免字符串转换开销let users = try decoder.decode([User].self, from: data)
大 JSON 考虑用流式解析,别一次性加载全部。
Core Data:
- 只取需要的字段(
propertiesToFetch)
let request = NSFetchRequest<NSDictionary>(entityName: "User")request.resultType = .dictionaryResultTyperequest.propertiesToFetch = ["id", "name"] // 只取需要的字段
SwiftUI:
- 用
@StateObject 代替 @ObservedObject,避免重复创建
struct ContentView: View {@StateObject private var viewModel = UserViewModel() // 只创建一次var body: some View {UserListView(users: viewModel.users) }}
最后
掌握这些优化技巧,不只是让代码跑得更快,更是让你成为一个更强的 iOS 开发者。
你遇到过哪些 Swift 性能问题?是怎么解决的?评论区聊聊。
红包封面还未领完,快领