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

SwiftUI视图展示锻炼数据前,如何确保Firebase Storage视频URL已完成更新?

SwiftUI视图展示锻炼数据前,如何确保Firebase Storage视频URL已完成更新?

我来帮你分析下问题所在,然后给出修复方案~

你当前的代码里,filterExercises函数有两个核心问题导致视图先拿到了没有视频URL的锻炼数据:

  1. 你在forEach里给每个锻炼单独开了Task去请求URL,但这些Task是后台异步执行的,函数本身直接返回了还没更新URL的过滤后数组,所以exercises会先被赋值为无URL的数据。
  2. 你在Task里修改的exercise是forEach循环里的副本,原数组里的元素根本没被更新;而且就算原元素被修改了,因为你只是修改数组内的元素,没有替换整个数组,@Published也不会触发视图更新。

修复方案

我们需要重构filterExercises函数,确保所有视频URL请求完成后,再返回完整的、带URL的锻炼数组,这样视图拿到的就是最终数据。这里用withTaskGroup来并发处理所有URL请求,既保证效率,又能等待所有请求完成:

import Combine
import FirebaseStorage
import Foundation

class ExerciseViewModel<DS: DataService>: ObservableObject where DS.Item == Exercise {
    @Published var exercises: [Exercise] = []

    private let dataService: DS
    private let storage = Storage.storage(url: "someURL")

    private var cancellables = Set<AnyCancellable>()

    init(dataService: DS, idArray: [String]) {
        self.dataService = dataService
        dataService.getData()
            .sink { error in
                print("Exercises request failed: \(String(describing: error))")
            } receiveValue: { [weak self] data in
                guard let self else { return }
                Task {
                    // 等待所有URL解析完成后,再给exercises赋值
                    self.exercises = await self.filterExercises(exercises: data, by: idArray)
                }
            }
            .store(in: &cancellables)
    }

    private func filterExercises(exercises: [Exercise], by idArray: [String]) async -> [Exercise] {
        // 第一步:先过滤出需要的锻炼数据
        let filteredExercises = exercises.filter { idArray.contains($0.id ?? "") }
        
        // 第二步:用TaskGroup并发处理所有视频URL请求,确保全部完成后返回
        return await withTaskGroup(of: Exercise.self) { group in
            for var exercise in filteredExercises {
                group.addTask { [weak self] in
                    guard let self else { return exercise }
                    do {
                        if let videoName = exercise.video, !videoName.isEmpty {
                            // 异步获取下载URL并更新到exercise对象
                            let downloadURL = try await self.storage.reference()
                                .child("video/\(videoName)")
                                .downloadURL()
                            exercise.videoUrl = downloadURL.absoluteString
                            print("Video URL resolved: \(exercise.videoUrl ?? "")")
                        }
                    } catch {
                        print("Error resolving video URL for exercise \(exercise.id ?? "unknown"): \(error)")
                    }
                    return exercise
                }
            }
            
            // 收集所有Task的结果,组成最终数组
            var updatedExercises: [Exercise] = []
            for await processedExercise in group {
                updatedExercises.append(processedExercise)
            }
            
            return updatedExercises
        }
    }
}

关键修改点说明:

  • 用withTaskGroup替代原来的forEach + 独立Task:TaskGroup可以让我们并发执行所有URL请求,同时等待所有任务完成后再收集结果,保证返回的数组每个元素都已经更新了视频URL。
  • 移除了原代码中self?.exercises = await self?.filterExercises(...) ?? []的可选链,改为先判断self存在再执行,避免不必要的可选值处理。
  • 每个TaskGroup的子任务负责处理单个锻炼的URL更新,返回更新后的对象,最后统一收集成数组再返回,这样赋值给exercises时,是直接替换整个数组,能正确触发@Published的视图更新通知。

这样修改后,你的视图就会在所有视频URL都解析完成后,才拿到完整的锻炼数据,不会再出现先展示无URL内容的问题啦~

备注:内容来源于stack exchange,提问作者Elena Rubilova

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 09:58:07