You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 10:12:54