如何为Decodable数据创建可复用的Firestore快照监听器函数
消除Firestore快照监听器的代码重复
问题背景
在ObservableObject子类Model中,为四个Firestore集合分别实现了快照监听器,代码高度重复——仅目标数组属性和数据类型存在差异。尝试用枚举定义集合,但不知道如何避免冗长的if/switch语句来映射数据类型与对应数组。
原始代码:
class Model: ObservableObject { @Published var tasks: [Task] = [] @Published var notes: [Note] = [] @Published var projects: [Project] = [] @Published var notebooks: [Notebook] = [] private var db = Firestore.firestore() init() { let _ = db.collection("tasks").whereField("userID", isEqualTo: Auth.auth().currentUser?.uid ?? "") .addSnapshotListener { querySnapshot, error in guard let documents = querySnapshot?.documents else { print("No documents in 'tasks' collection") return } self.tasks = documents.compactMap { queryDocumentSnapshot in try? queryDocumentSnapshot.data(as: Task.self) } } let _ = db.collection("projects").whereField("userID", isEqualTo: Auth.auth().currentUser?.uid ?? "") .addSnapshotListener { querySnapshot, error in guard let documents = querySnapshot?.documents else { print("No documents in 'projects' collection") return } self.projects = documents.compactMap { queryDocumentSnapshot in try? queryDocumentSnapshot.data(as: Project.self) } } let _ = db.collection("notes").whereField("userID", isEqualTo: Auth.auth().currentUser?.uid ?? "") .addSnapshotListener { querySnapshot, error in guard let documents = querySnapshot?.documents else { print("No documents in 'notes' collection") return } self.notes = documents.compactMap { queryDocumentSnapshot in try? queryDocumentSnapshot.data(as: Note.self) } } let _ = db.collection("notebooks").whereField("userID", isEqualTo: Auth.auth().currentUser?.uid ?? "") .addSnapshotListener { querySnapshot, error in guard let documents = querySnapshot?.documents else { print("No documents in 'notebooks' collection") return } self.notebooks = documents.compactMap { queryDocumentSnapshot in try? queryDocumentSnapshot.data(as: Notebook.self) } } } }
尝试的枚举方案:
enum Collection: String, CaseIterable { case tasks = "tasks", case notes = "notes", case projects = "projects", case notebooks = "notebooks" } for collection in Collection.allCases { // Add snapshot listener }
解决方案
1. 统一数据模型协议
确保所有Firestore数据模型(Task/Note/Project/Notebook)遵循Codable(Firestore的data(as:)方法依赖此协议),可以用一个空协议统一标识:
protocol FirestoreModel: Codable {} // 扩展每个模型以遵守协议 extension Task: FirestoreModel {} extension Note: FirestoreModel {} extension Project: FirestoreModel {} extension Notebook: FirestoreModel {}
2. 封装泛型监听器逻辑
在Model中编写一个泛型方法,将重复的监听器逻辑抽离出来,接收集合名称、目标数组的KeyPath和数据类型:
private func setupSnapshotListener<T: FirestoreModel>(for collectionName: String, updating keyPath: ReferenceWritableKeyPath<Model, [T]>) { guard let userId = Auth.auth().currentUser?.uid else { print("未认证用户,跳过\(collectionName)集合监听器") return } db.collection(collectionName) .whereField("userID", isEqualTo: userId) .addSnapshotListener { [weak self] snapshot, error in guard let self = self else { return } if let error = error { print("获取\(collectionName)数据失败:\(error.localizedDescription)") return } guard let documents = snapshot?.documents else { print("\(collectionName)集合无文档") return } // 解析文档并更新对应@Published属性 self[keyPath: keyPath] = documents.compactMap { try? $0.data(as: T.self) } } }
3. 批量初始化监听器
直接定义包含所有集合元数据的数组,遍历调用泛型方法,彻底消除重复代码:
class Model: ObservableObject { @Published var tasks: [Task] = [] @Published var notes: [Note] = [] @Published var projects: [Project] = [] @Published var notebooks: [Notebook] = [] private var db = Firestore.firestore() init() { // 集合元数据:(集合名称, 目标数组KeyPath) let collectionConfigs = [ ("tasks", \.tasks), ("notes", \.notes), ("projects", \.projects), ("notebooks", \.notebooks) ] // 遍历配置,批量设置监听器 for (name, keyPath) in collectionConfigs { setupSnapshotListener(for: name, updating: keyPath) } } // 泛型监听器方法(同上) private func setupSnapshotListener<T: FirestoreModel>(for collectionName: String, updating keyPath: ReferenceWritableKeyPath<Model, [T]>) { // ... 实现代码 ... } }
可选:用枚举优化集合管理
如果希望用枚举统一管理集合名称,可修改枚举为带关联值的类型,直接绑定数据类型和KeyPath:
enum FirestoreCollection { case tasks(ReferenceWritableKeyPath<Model, [Task]>) case notes(ReferenceWritableKeyPath<Model, [Note]>) case projects(ReferenceWritableKeyPath<Model, [Project]>) case notebooks(ReferenceWritableKeyPath<Model, [Notebook]>) var name: String { switch self { case .tasks: return "tasks" case .notes: return "notes" case .projects: return "projects" case .notebooks: return "notebooks" } } static var allCases: [FirestoreCollection] { [ .tasks(\.tasks), .notes(\.notes), .projects(\.projects), .notebooks(\.notebooks) ] } }
然后在init中遍历枚举:
init() { for collection in FirestoreCollection.allCases { switch collection { case .tasks(let path): setupSnapshotListener(for: collection.name, updating: path) case .notes(let path): setupSnapshotListener(for: collection.name, updating: path) case .projects(let path): setupSnapshotListener(for: collection.name, updating: path) case .notebooks(let path): setupSnapshotListener(for: collection.name, updating: path) } } }
这种方式更便于统一管理集合名称,后续新增集合只需在枚举中添加一个case即可。
内容的提问来源于stack exchange,提问作者user19555877
相关产品推荐
相关产品推荐

