Firestore与Swift:如何正确获取Workouts集合中的关联Exercises数据
我在Firestore中有两个集合:Workouts和Exercises。Workouts集合的文档包含name、type字段,以及一个指向Exercises集合中文档的引用数组exercises。
尝试用Swift代码获取Workouts数据时,注释掉WorkoutCodable中的var exercises: [ExerciseCodable]字段解码正常,取消注释则出现错误:
Error getting the benchmark workouts: Swift.DecodingError.typeMismatch(Swift.Dictionary<Swift.String, Any>, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "exercises", intValue: nil), _JSONKey(stringValue: "Index 0", intValue: 0)], debugDescription: "Expected to decode Dictionary<String, Any> but found FIRDocumentReference instead.", underlyingError: nil))
错误原因
Firestore中exercises数组存储的是**DocumentReference类型的文档引用**,而直接尝试将其解码为ExerciseCodable对象会导致类型不匹配,触发解码失败。需要先处理引用,再获取对应的关联文档数据。
方案一:先解码引用,批量获取关联数据
这是更推荐的异步处理方式,避免主线程阻塞:
- 修改
WorkoutCodable,先存储文档引用数组:
struct WorkoutCodable: Identifiable, Codable, Hashable { @DocumentID var id: String? var exercises: [DocumentReference] // 先存储引用 var name: String? var type: WorkoutTypeCodable // 可选:添加存储属性保存解析后的Exercise对象 var exerciseObjects: [ExerciseCodable]? }
- 获取Workout后,批量获取关联的Exercise数据:
do { let workoutQuerySnapshot = try await store.collection("workouts").getDocuments() for workoutDoc in workoutQuerySnapshot.documents { var workout = try workoutDoc.data(as: WorkoutCodable.self) // 批量获取所有Exercise引用对应的文档 let exerciseRefs = workout.exercises let exerciseDocs = try await store.getAllDocuments(for: exerciseRefs) // 将文档解码为ExerciseCodable对象 workout.exerciseObjects = try exerciseDocs.map { try $0.data(as: ExerciseCodable.self) } // 此时workout.exerciseObjects就是对应的关联数据 } } catch { print("获取数据失败:\(error)") }
方案二:自定义解码逻辑,自动解析引用为对象
通过自定义init(from decoder:)方法,在解码Workout时自动获取关联的Exercise数据:
struct WorkoutCodable: Identifiable, Codable, Hashable { @DocumentID var id: String? var exercises: [ExerciseCodable] var name: String? var type: WorkoutTypeCodable enum CodingKeys: String, CodingKey { case id, exercises, name, type } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decodeIfPresent(String.self, forKey: .id) name = try container.decodeIfPresent(String.self, forKey: .name) type = try container.decode(WorkoutTypeCodable.self, forKey: .type) // 解码文档引用数组 let refs = try container.decode([DocumentReference].self, forKey: .exercises) // 同步获取每个引用对应的文档(注意:大量数据时建议改用异步逻辑) let exerciseDocs = try refs.map { try $0.getDocument() } // 解码为ExerciseCodable对象 exercises = try exerciseDocs.map { try $0.data(as: ExerciseCodable.self) } } }
注意事项
- 方案二中的同步获取文档会阻塞当前线程,若
exercises数组元素较多,建议改用异步处理(比如将exercises声明为[ExerciseCodable?],后续通过Combine或Task异步填充),避免界面卡顿。 - 若需要频繁访问关联数据,可考虑在Firestore中做数据冗余(即把Exercise的部分字段直接存在Workout文档中),减少读取次数,提升性能。
内容的提问来源于stack exchange,提问作者noloman

