You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Firestore与Swift:如何正确获取Workouts集合中的关联Exercises数据

解决Firestore中文档引用数组的解码与关联数据获取问题

我在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对象会导致类型不匹配,触发解码失败。需要先处理引用,再获取对应的关联文档数据。

方案一:先解码引用,批量获取关联数据

这是更推荐的异步处理方式,避免主线程阻塞:

  1. 修改WorkoutCodable,先存储文档引用数组:
struct WorkoutCodable: Identifiable, Codable, Hashable {
    @DocumentID var id: String?
    var exercises: [DocumentReference] // 先存储引用
    var name: String?
    var type: WorkoutTypeCodable
    
    // 可选:添加存储属性保存解析后的Exercise对象
    var exerciseObjects: [ExerciseCodable]?
}
  1. 获取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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 07:05:01