Swift:等待所有GooglePlaces API请求完成后再更新Published变量
解决Google Places API图片加载后一次性更新UI的问题
方案一:使用DispatchGroup等待所有请求完成
通过DispatchGroup追踪所有图片请求的完成状态,等全部请求结束后再一次性更新cardModels,避免UI逐个刷新的堆叠效果:
import Foundation import GooglePlaces class CardsViewModel: ObservableObject { @Published var cardModels = [CardModel]() init() { GooglePlacesAPI().performNearbyRestaurantSearch { gmsplaces in var models = [CardModel]() let group = DispatchGroup() gmsplaces.forEach { place in guard let placeId = place.placeID else { return } group.enter() GooglePlacesAPI().getPhotoFromMetaData(id: placeId) { image in defer { group.leave() } guard let image = image else { return } let restaurant = RestaurantModel( id: placeId, name: place.name, uiimages: [image] ) models.append(CardModel(restaurant: restaurant)) } } group.notify(queue: .main) { self.cardModels = models } } } }
核心要点:
- 用
DispatchGroup管理所有图片请求:每个请求开始前调用group.enter(),请求完成(无论成功失败)调用group.leave() - 所有请求结束后,
group.notify会在主队列触发,此时将收集好的完整模型数组赋值给cardModels,实现一次性UI更新 - 增加
guard let处理可选值,避免强制解包引发崩溃
方案二:使用Async/Await(iOS 15+)
如果项目支持iOS 15及以上,推荐用Swift原生的async/await语法,代码更简洁易读:
首先将原有的回调式API包装为异步函数:
extension GooglePlacesAPI { func performNearbyRestaurantSearch() async throws -> [GMSPlace] { return try await withCheckedThrowingContinuation { continuation in self.performNearbyRestaurantSearch { places in continuation.resume(returning: places) } } } func getPhotoFromMetaData(id: String) async -> UIImage? { return await withCheckedContinuation { continuation in self.getPhotoFromMetaData(id: id) { image in continuation.resume(returning: image) } } } }
然后修改ViewModel:
import Foundation import GooglePlaces class CardsViewModel: ObservableObject { @Published var cardModels = [CardModel]() init() { Task { do { let gmsplaces = try await GooglePlacesAPI().performNearbyRestaurantSearch() var models = [CardModel]() for place in gmsplaces { guard let placeId = place.placeID else { continue } if let image = await GooglePlacesAPI().getPhotoFromMetaData(id: placeId) { let restaurant = RestaurantModel( id: placeId, name: place.name, uiimages: [image] ) models.append(CardModel(restaurant: restaurant)) } } DispatchQueue.main.async { self.cardModels = models } } catch { print("搜索附近餐厅失败:\(error)") } } } }
并行优化(可选)
如果想让图片请求并行执行以缩短加载时间,可使用TaskGroup:
// 替换Task内部的遍历逻辑 let models = try await withThrowingTaskGroup(of: CardModel?.self) { group in for place in gmsplaces { guard let placeId = place.placeID else { continue } group.addTask { guard let image = await GooglePlacesAPI().getPhotoFromMetaData(id: placeId) else { return nil } let restaurant = RestaurantModel( id: placeId, name: place.name, uiimages: [image] ) return CardModel(restaurant: restaurant) } } var result = [CardModel]() for try await model in group { if let model = model { result.append(model) } } return result } DispatchQueue.main.async { self.cardModels = models }
所有图片请求会并行发起,最终仍一次性更新UI,兼顾速度和视觉效果。
内容的提问来源于stack exchange,提问作者Chris Ho
相关产品推荐
相关产品推荐

