我正在用 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) 几乎相同。
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)
}
}
}
我认为这里最好的解决方案是添加一个超类,您的所有 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"
}
}