如何在Swift中泛化Firestore对象行为以避免重复代码?
Swift Firestore可解码对象的代码泛化方案
问题场景
你正在开发iOS应用,用Firestore做数据库,所有Firestore对象的代码模式一致:包含@DocumentID的id属性、若干@Published业务属性,需要实现Decodable、Hashable、ObservableObject,且hash、==方法逻辑完全复用,init(from:)仅属性和编码键不同,希望避免重复代码,同时自动生成类似Swift默认Decodable的初始化方法。
原代码示例:
import FirebaseFirestoreSwift import Combine class FirestoreObject: Decodable, Hashable, ObservableObject { enum CodingKeys: String, CodingKey { case id case attributeA = "attribute_a" case attributeB = "attribute_b" } @DocumentID var id: String? @Published var attributeA: Double @Published var attributeB: String required init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) _id = try container.decode(DocumentID<String>.self, forKey: .id) self.attributeA = try container.decode(Double.self, forKey: .attributeA) self.attributeB = try container.decode(String.self, forKey: .attributeB) } func hash(into hasher: inout Hasher) { hasher.combine(id) } static func == (lhs: FirestoreObject, rhs: FirestoreObject) -> Bool { return lhs.id == rhs.id } }
解决方案
1. 定义通用基类
创建基类封装所有Firestore对象的通用逻辑,包括@DocumentID属性、Hashable和Equatable的实现,利用Swift自动合成Decodable初始化的特性:
import FirebaseFirestoreSwift import Combine class BaseFirestoreObject: ObservableObject, Hashable, Decodable { // 自动映射Firestore文档ID,无需手动解码 @DocumentID var id: String? // MARK: - Hashable & Equatable func hash(into hasher: inout Hasher) { hasher.combine(id) } static func == (lhs: BaseFirestoreObject, rhs: BaseFirestoreObject) -> Bool { lhs.id == rhs.id } // 空初始化,用于Swift自动合成Decodable初始化方法 init() {} // 让Swift自动合成Decodable初始化,无需手动实现 // Firestore的解码机制会自动处理@DocumentID属性 required init(from decoder: Decoder) throws {} }
2. 编写子类实现业务对象
子类只需继承基类,定义自身的@Published业务属性和CodingKeys即可,Swift会自动合成完整的Decodable初始化方法,无需编写init(from:)、hash和==:
class User: BaseFirestoreObject { enum CodingKeys: String, CodingKey { case username = "user_name" case age } @Published var username: String @Published var age: Int } class Product: BaseFirestoreObject { enum CodingKeys: String, CodingKey { case name case price = "product_price" case stock } @Published var name: String @Published var price: Double @Published var stock: Int }
3. 解码使用示例
使用Firestore的DocumentSnapshot.data(as:)方法解码时,会自动处理@DocumentID属性,无需额外配置:
func fetchUser(document: DocumentSnapshot) async throws -> User { guard let user = try document.data(as: User.self) else { throw NSError(domain: "Firestore", code: -1, userInfo: [NSLocalizedDescriptionKey: "Failed to decode user"]) } return user }
关键说明
- @DocumentID自动映射:Firestore的解码机制会自动将文档ID赋值给
@DocumentID标记的属性,无需在CodingKeys中声明id字段,也无需手动解码。 - 自动合成Decodable:只要子类的所有
@Published属性类型遵循Decodable(基本类型如String、Double、Int都默认支持),Swift就会自动生成完整的init(from:)方法,包含基类和子类的所有属性解码。 - 代码复用:基类一次性实现了
Hashable、Equatable的核心逻辑,子类仅需关注业务属性的定义,彻底消除重复代码。
内容的提问来源于stack exchange,提问作者Dawson Eliasen
相关产品推荐
相关产品推荐

