如何编写低样板代码的Swift GRDB模型(支持自动更新与JSON加载)
优化GRDB模型,减少样板代码
针对你遇到的每个字段重复多次的问题,可以通过以下几个方向简化代码:
1. 利用Swift自动合成Codable实现
只要你的CodingKeys枚举(如果需要自定义键名)或属性名称与JSON键名完全匹配,Swift会自动为你生成init(from:)和encode(to:)方法,无需手动编写。
2. 利用GRDB的自动合成能力
GRDB支持自动合成FetchableRecord和PersistableRecord的核心方法,只要Columns枚举的case名称和属性名一致,就不用手动实现init(row:)和encode(to:inout PersistenceContainer)。
3. 用属性包装器复用自动保存逻辑
把@Published的didSet自动保存逻辑封装成属性包装器,避免每个字段都重复写didSet { save() }。
优化后的完整代码
import Foundation import GRDB // 自定义属性包装器,封装@Published与自动保存逻辑 @propertyWrapper struct AutoSavePublished<Value> { @Published var wrappedValue: Value private let saveAction: () -> Void init(wrappedValue: Value, saveAction: @escaping () -> Void) { self.wrappedValue = wrappedValue self.saveAction = saveAction } // 保留@Published的投影值用法(如$name) var projectedValue: Published<Value>.Publisher { $wrappedValue } mutating func setValue(_ value: Value) { wrappedValue = value saveAction() } } class Routine: Identifiable, Codable, FetchableRecord, PersistableRecord, ObservableObject { var id: UUID = UUID() @AutoSavePublished var name: String // GRDB表名定义 static let databaseTableName = "routines" // 类型安全列枚举,与属性名一致即可 enum Columns: String, ColumnExpression { case id, name } // 自定义初始化器 init(id: UUID, name: String) { self.id = id self._name = AutoSavePublished(wrappedValue: name, saveAction: self.save) } // 自动保存方法 func save() { do { try DatabaseManager.shared.dbQueue.write { db in try self.save(db) } } catch { print("Error saving routine:", error) } } }
关键优化点说明
- Codable简化:删除了手动编写的
CodingKeys、init(from:)和encode(to:),Swift会根据属性名自动生成这些代码;如果需要自定义JSON键名,仅需添加CodingKeys枚举即可,无需手动实现编码解码逻辑。 - GRDB方法自动合成:GRDB会检测到
Columns枚举的case与属性名匹配,自动为你实现数据库行的读取和写入逻辑,无需手动编写字段映射。 - 属性包装器复用逻辑:
AutoSavePublished包装器把@Published和自动保存的逻辑封装在一起,新增字段时只需要用@AutoSavePublished修饰,不用再重复写didSet代码。
自定义JSON键名的情况
如果你的模型字段需要自定义JSON键名,仅需添加CodingKeys枚举,Swift依然会自动合成编码解码方法:
extension Routine { enum CodingKeys: String, CodingKey { case id case name = "routine_display_name" // 自定义JSON键名 } }
内容的提问来源于stack exchange,提问作者Vedmant
相关产品推荐
相关产品推荐

