firebase 如何在Swift中泛化Firestore Object行为以避免重复?

aydmsdu9  于 2023-03-24  发布在  Swift
关注(0)|答案(2)|浏览(195)

我正在用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(来自decoder:)将总是完全相同的工作,尽管当然键/属性因对象而异。
我研究了协议和继承,但我对Swift还是新手,我不确定正确的方法。主要的事情是尝试自动提供默认init,它按照我想要的方式工作--它与默认init几乎相同(来自:decoder),Swift为Decodable提供的功能。

qoefvg9y

qoefvg9y1#

Firestore支持Swift的Codable协议,这使得Map更加容易:在大多数情况下,您不必编写任何Map代码。只有当您有特殊要求时(例如,仅Map某些文档属性,或将属性Map到名称略有不同的属性),您才需要添加几行代码来告诉Codable要Map哪些字段或Map到哪些属性。
我们的文档提供了一个全面的指南,解释了Codable的基础知识,以及一些更高级的用例:Map Cloud Firestore data with Swift Codable  |  Firebase
在SwiftUI应用程序中,您希望数据模型是结构体,只有视图模型应该是符合ObservableObjectclass
下面是一个例子:

数据模型

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

nfzehxib2#

我认为这里最好的解决方案是添加一个超类,所有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并将它们 Package 在一个符合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"
    }
}

相关问题