第 9 章:性能优化与调试
避免常见性能陷阱
1. 批量操作与批处理
SwiftData 的性能瓶颈常出现在高频的单次操作中。应优先采用批量处理策略:
// 反模式:循环中单次插入
for item in items {
context.insert(item) // 每次插入都会触发持久化层操作
}
// 推荐:批量插入后统一保存
items.forEach { context.insert($0) }
try? context.save() // 单次持久化操作
2. 合理使用 Fetch 请求
不当的查询方式会导致性能急剧下降:
- 谓词优化:使用
#Predicate时确保条件可被索引
// 低效:无法利用索引的模糊查询
#Predicate<Book> { $0.title.contains("swift") }
// 高效:精确匹配可索引字段
#Predicate<Book> { $0.isbn == "1234567890" }
- 分页加载:大数据集必须使用 limit/offset
let fetchDescriptor = FetchDescriptor<Book>(
predicate: #Predicate { $0.category == "programming" },
sortBy: [SortDescriptor(\.title)],
limit: 20,
offset: currentPage * 20
)
3. 关系加载策略
默认的延迟加载可能导致 N+1 查询问题:
@Model class Author {
@Relationship(deleteRule: .cascade, inverse: \Book.author)
var books: [Book] = []
}
// 反模式:遍历访问未加载的关系
let authors = try context.fetch(FetchDescriptor<Author>())
authors.forEach { author in
print(author.books.count) // 每次访问都会触发独立查询
}
// 解决方案1:预加载关系
let descriptor = FetchDescriptor<Author>(
fetchLimit: 50,
includingPendingChanges: true
)
descriptor.relationshipKeyPathsForPrefetching = [\Author.books]
// 解决方案2:使用@Query的fetchBatchSize
@Query(fetchBatchSize: 25) var authors: [Author]
4. 内存管理技巧
- 及时释放:对于只读操作的对象,可主动转为 Fault
context.refresh(author, mergeChanges: false)
- 后台队列处理:耗时操作应放在后台上下文
Task.detached(priority: .utility) {
let bgContext = ModelContext(modelContainer)
// 执行批量处理...
try? bgContext.save()
}
5. 模型设计优化
- 避免过度规范化:适当的数据冗余可减少关联查询
- 慎用 Transformable:
Codable类型的序列化/反序列化成本较高
// 谨慎使用:频繁访问的复杂结构
@Attribute(.transformable(by: MyDataTransformer.self))
var metadata: [String: Any]?
// 推荐替代方案:分解为实体关系
@Relationship var tags: [Tag]
性能检查清单
| 检查项 | 优化手段 |
|---|---|
| 频繁的小规模操作 | 合并为批量操作 |
| 大数据集遍历 | 使用 fetchBatchSize 或分页 |
| 复杂关系访问 | 预加载 relationshipKeyPaths |
| 主线程长时间阻塞 | 移至后台上下文处理 |
| 高频更新的属性 | 考虑是否需持久化或改为计算属性 |
最佳实践提示:在开发阶段开启
-com.apple.CoreData.SQLDebug 1环境变量,可在控制台查看实际生成的 SQL 语句,帮助识别性能热点。
