如何让FirebaseObject子类的闭包回调直接返回子类类型?
Solution for Type-Safe Callback Closures in FirebaseObject Subclasses
Hey there! I’ve run into similar generic inheritance quirks with Firebase models before, so let’s break down how to fix this and get type-safe callbacks without forced casts.
The Core Issue with Your Previous Attempts
- Using
RecipeItem: FirebaseObject<RecipeItem>(recursive generics) likely crashed because of improper initialization chaining—if your parent class tried to create an instance of the generic type during setup, it would loop infinitely. - Using
FirebaseObject<RecipeItem.Type>was a misstep because you were passing the class type instead of the instance type, hence the "Instance member cannot be used on type" error.
The Correct Generic Approach
We’ll define the parent class with a generic parameter constrained to itself (a recursive generic constraint), then use that parameter to type our callbacks. This lets us pass concrete subclass instances directly to callbacks without casting.
Step 1: Redefine the FirebaseObject Parent Class
class FirebaseObject<SelfType: FirebaseObject<SelfType>> { // Store your update callbacks with the subclass type private var updateCallbacks: [(SelfType) -> Void] = [] // Add a type-safe callback method func addUpdateCallback(_ callback: @escaping (SelfType) -> Void) { updateCallbacks.append(callback) } // Internal method to trigger callbacks when data updates func triggerDataUpdate() { // Safe cast (this will never fail if subclasses are defined correctly) guard let typedSelf = self as? SelfType else { fatalError("Subclass must declare itself as the generic type (e.g., RecipeItem: FirebaseObject<RecipeItem>)") } updateCallbacks.forEach { $0(typedSelf) } } // Base initializer (adjust to match your Firebase setup) init() { // Add your Firebase reference setup or base initialization logic here } }
Step 2: Define Your Subclasses
Each subclass simply declares itself as the generic argument for FirebaseObject:
class RecipeItem: FirebaseObject<RecipeItem> { var title: String = "" var cookingTime: Int = 0 // Add any other Recipe-specific properties/methods } class User: FirebaseObject<User> { var username: String = "" var email: String = "" // Add any other User-specific properties/methods }
Step 3: Use Type-Safe Callbacks
Now you can use callbacks without any forced casting, and the compiler will enforce correct type access:
let myRecipe = RecipeItem() myRecipe.addUpdateCallback { updatedRecipe in // Directly access subclass properties—no casts needed! print("Updated recipe title: \(updatedRecipe.title)") print("Updated cooking time: \(updatedRecipe.cookingTime)") } // Simulate a Firebase data update (call this when your model refreshes) myRecipe.triggerDataUpdate()
Key Notes for Success
- Initializer Safety: Ensure your parent class’s initializer doesn’t attempt to create an instance of
SelfType—this would cause infinite recursion. Keep base initialization logic focused on Firebase setup, not subclass-specific work. - Firebase Snapshot Initialization: If you initialize models from Firebase snapshots, add a failable initializer to the parent class and override it in subclasses:
// In FirebaseObject init?(snapshot: DataSnapshot) { super.init() // Add base snapshot parsing logic if needed } // In RecipeItem override init?(snapshot: DataSnapshot) { super.init(snapshot: snapshot) guard let values = snapshot.value as? [String: Any], let title = values["title"] as? String, let cookingTime = values["cookingTime"] as? Int else { return nil } self.title = title self.cookingTime = cookingTime } - Minimal Subclass Work: New subclasses only need to inherit with their own generic type and add their properties—no extra code for callbacks required, which fits your requirement of keeping logic in the parent class.
内容的提问来源于stack exchange,提问作者Casper Zandbergen
相关产品推荐
相关产品推荐

