如何在 Swift 中概括 Firestore 对象的行为以避免重复?

问题描述 投票:0回答:2

我正在用 Swift 编写一个 iOS 应用程序,使用 Firestore 作为数据库。我有代表我的 Firestore 对象的类,如下所示:

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
    }
    
}

此模式对于我的所有 Firestore 对象都是相同的。我如何概括这一点以避免重复自己?散列和 == 函数总是可以完全相同,并且 init(from decoder:) 将始终完全相同,尽管键/属性当然因对象而异。

我研究了协议和继承,但我对 Swift 还是个新手,我不确定正确的方法。最主要的是尝试自动提供按我想要的方式工作的默认 init——它与 swift 为 Decodable 提供的默认 init(from: decoder) 几乎相同。

ios swift firebase google-cloud-firestore
2个回答
0
投票

Firestore 支持 Swift 的

Codable
协议,这使得映射变得更加容易:在大多数情况下,您不必编写任何映射代码。仅当您有特殊要求时(例如,仅映射某些文档属性,或将属性映射到名称略有不同的属性),您才需要添加几行代码来告诉 Codable 要映射哪些字段,或要映射到哪些属性.

我们的文档有一个全面的指南,解释了

Codable
的基础知识,以及一些更高级的用例:使用 Swift Codable 映射 Cloud Firestore 数据 | Firebase

在 SwiftUI 应用程序中,您会希望数据模型是结构体,只有视图模型应该是符合

class
.
ObservableObject

举个例子:

数据模型

struct ProgrammingLanguage: Identifiable, Codable {
  @DocumentID var id: String?
  var name: String
  var year: Date
  var reasonWhyILoveThis: String = ""
}

查看模型

class ProgrammingLanguagesViewModel: ObservableObject {
  @Published var programmingLanguages = [ProgrammingLanguage]()
  @Published var newLanguage = ProgrammingLanguage.empty
  @Published var errorMessage: String?
  
  private var db = Firestore.firestore()
  private var listenerRegistration: ListenerRegistration?
  
  fileprivate  func unsubscribe() {
    if listenerRegistration != nil {
      listenerRegistration?.remove()
      listenerRegistration = nil
    }
  }
  
  fileprivate func subscribe() {
    if listenerRegistration == nil {
      listenerRegistration = db.collection("programming-languages")
        .addSnapshotListener { [weak self] (querySnapshot, error) in
          guard let documents = querySnapshot?.documents else {
            self?.errorMessage = "No documents in 'programming-languages' collection"
            return
          }
          
          self?.programmingLanguages = documents.compactMap { queryDocumentSnapshot in
            let result = Result { try queryDocumentSnapshot.data(as: ProgrammingLanguage.self) }
            
            switch result {
            case .success(let programmingLanguage):
              // A ProgrammingLanguage value was successfully initialized from the DocumentSnapshot.
              self?.errorMessage = nil
              return programmingLanguage
            case .failure(let error):
              self?.errorMessage = "Error decoding document: \(error.localizedDescription)"
              return nil
            }
          }
        }
    }
  }
  
  fileprivate func addLanguage() {
    let collectionRef = db.collection("programming-languages")
    do {
      let newDocReference = try collectionRef.addDocument(from: newLanguage)
      print("ProgrammingLanguage stored with new document reference: \(newDocReference)")
    }
    catch {
      print(error)
    }
  }
}

-1
投票

我认为这里最好的解决方案是添加一个超类,您的所有 Firestore 类型都继承自该超类

class SuperFirestoreObject: ObservableObject, Decodable {
    @DocumentID var id: String?
    
    enum CodingKeys: String, CodingKey { case id }
    
    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        _id = try container.decode(DocumentID.self, forKey: .id)
    }
    
    func hash(into hasher: inout Hasher) {
        hasher.combine(id)
    }
    
    static func == (lhs: SuperFirestoreObject, rhs: SuperFirestoreObject) -> Bool {
        return lhs.id == rhs.id
    }
}

然后你需要在所有子类中添加一个

init(from:)
来解码该子类的特定属性

class FirestoreObject: SuperFirestoreObject {
    @Published var attributeA: Double
    @Published var attributeB: String

    enum CodingKeys: String, CodingKey {
        case attributeA = "attribute_a"
        case attributeB = "attribute_b"
    }

    required init(from decoder: Decoder) throws {
        let container = try decoder.container(keyedBy: CodingKeys.self)
        self.attributeA = try container.decode(Double.self, forKey: .attributeA)
        self.attributeB = try container.decode(String.self, forKey: .attributeB)
        try super.init(from: decoder)
    }
}

我不认为你可以概括它更多,除非你可以使用 struct 并将它们包装在一个符合 ObservableObject 的通用类中,但这样只会发布对象而不是单个属性。

有点像

class FirestoreObjectHolder<FirestoreObject>: ObservableObject where FirestoreObject: Hashable, FirestoreObject: Decodable {
    @Published var object: FirestoreObject

    init(object: FirestoreObject) {
        self.object = object
    }
}

那么实际的类型就很容易实现了

struct FirestoreObjectAsStruct: Hashable, Decodable {
    @DocumentID var id: String?
    let attributeA: Double
    let attributeB: String

    enum CodingKeys: String, CodingKey {
        case attributeA = "attribute_a"
        case attributeB = "attribute_b"
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.