使用 Struct 和 Class 管理数据
概述
在 SwiftUI 的 MVVM 架构中,Model 层是数据管理的核心。Swift 提供了 Struct 和 Class 两种主要的数据结构来定义模型,它们各有特点,适用于不同的场景。本节将深入探讨如何选择和使用这两种类型来构建高效、可维护的数据模型。
Struct 与 Class 的特性对比
1. 值类型 vs 引用类型
Struct(值类型)
- 赋值或传递时会发生拷贝
- 线程安全(默认隔离修改)
- 适用于简单、独立的数据模型
- 示例:
struct TodoItem: Identifiable { let id: UUID var title: String var isCompleted: Bool }
Class(引用类型)
- 赋值或传递时共享同一实例
- 需要手动处理线程安全
- 适用于需要共享或继承的场景
- 示例:
class UserProfile: ObservableObject { @Published var name: String @Published var settings: UserSettings }
2. 选择标准
| 场景 | 推荐类型 | 理由 |
|---|---|---|
| 不可变数据模型 | Struct | 值语义避免意外修改 |
| 需要共享状态 | Class | 引用语义允许多组件访问同一数据 |
| 配合 SwiftUI 状态管理 | Struct | 与 @State 和 @Binding 天然兼容 |
| 需要继承或复用逻辑 | Class | 支持面向对象特性 |
在 MVVM 中的实践
1. Model 层的典型实现
// 使用 Struct 定义核心数据模型
struct Product: Codable {
var id: Int
var name: String
var price: Double
}
// 使用 Class 实现可观察的业务逻辑
class InventoryViewModel: ObservableObject {
@Published var products: [Product] = []
func loadProducts() async {
// 网络请求示例
guard let url = URL(string: "api/products") else { return }
do {
let (data, _) = try await URLSession.shared.data(from: url)
products = try JSONDecoder().decode([Product].self, from: data)
} catch {
print("加载失败: \(error)")
}
}
}
2. 最佳实践建议
优先使用 Struct
除非需要引用语义,否则默认选择值类型以获得更好的性能和安全性。Class 的使用场景
- 需要实现
ObservableObject协议的 ViewModel - 需要跨多个 View 共享的状态容器
- 复杂业务逻辑的封装
- 需要实现
性能优化技巧
// 对于大型数据集,使用引用类型包装 class LargeDataSetWrapper { var items: [BigStruct] = [] }
常见问题与解决方案
问题1:何时该将 Struct 改为 Class?
场景:当发现多个视图需要同步修改同一数据源时
解决方案:
- 将模型改为
class并遵循ObservableObject - 使用
@StateObject或@ObservedObject注入依赖
问题2:如何避免 Struct 的意外拷贝?
技巧:
- 对于频繁修改的大数据集合,使用
inout参数 - 实现
Copy-on-Write模式:struct COWWrapper<T> { private var storage: T mutating func modify(_ changes: (inout T) -> Void) { if !isKnownUniquelyReferenced(&storage) { storage = storage } changes(&storage) } }
案例:Todo 数据模型的实现
// Model 层
struct Todo: Identifiable, Equatable {
let id = UUID()
var description: String
var priority: Priority // enum 类型
}
// ViewModel 层
class TodoListViewModel: ObservableObject {
@Published private(set) var todos: [Todo] = []
func addTodo(_ description: String) {
let newTodo = Todo(description: description, priority: .medium)
todos.append(newTodo)
}
}
关键点:Model 使用 Struct 保证数据不可变性,ViewModel 使用 Class 管理可变状态。
进阶思考
- 性能分析工具:使用 Instruments 的 "Allocations" 工具跟踪 Struct/Class 的内存使用
- Swift 编译器优化:通过
@_optimize(none)禁止特定方法的编译器优化进行调试 - 与 Combine 集成:当 Struct 需要发布变化时,可以将其包装在
CurrentValueSubject中
通过合理选择数据类型,可以构建出既高效又易于维护的 MVVM 应用架构。
