I am writing an iOS app in Swift, using Firestore as a database. I have classes representing my Firestore objects that look like this:
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
}
}
This pattern is the same for all of my Firestore objects. How can I generalize this to avoid repeating myself? The hash and the == functions can always be exactly the same, and the init(from decoder:) will always work exactly the same, although of course the keys/attributes differ from object to object.
I looked into protocols and inheritance, but I’m still new to Swift and I’m not sure the right way to go about it. The main thing is trying to automatically provide the default init that works the way I want to — it’s pretty much the same as the default init(from: decoder) that swift provides for Decodable.
2
Answers
I think the best solution here is to add a super class that all your Firestore types inherits from
Then you will need to add a
init(from:)
in all the subclasses to decode the specific attributes for that sub classI don’t think you can generalise it much more than that unless you could use struct and wrap them in a generic class that conforms to ObservableObject but then only the object would be published and not the individual properties.
Something like
Then the actual type would be quite easy to implement
Firestore supports Swift’s
Codable
protocol, which makes mapping a lot easier: For most cases, you won’t have to write any mapping code. Only if you have special requirements (e.g. mapping only some document attributes, or mapping attributes to property with a slightly different name), you will have to add a few lines of code to tell Codable which fields to map, or which properties to map to.Our documentation has a comprehensive guide that explains the basics of
Codable
, and some of the more advanced use cases: Map Cloud Firestore data with Swift Codable | FirebaseIn a SwiftUI app, you will want your data model to be structs, and only the view model should be a
class
conforming toObservableObject
.Here is an example:
Data model
View Model