Swift中Task/Async未后台运行阻塞主线程,Feed加载卡顿求助
优化方案
1. 修复异步任务的线程安全问题
ViewModel中用Task.detached会脱离主线程上下文,直接在后台更新@Published属性会导致线程不安全,进而触发不必要的UI刷新。修改异步方法,确保在主线程更新UI状态:
func getFeed() async { let result = await feedUseCase.getFeed() switch result { case .success(let feed): await MainActor.run { // 仅当数据变化时更新,避免重复触发刷新 if self.feed != feed { self.feed = feed } } case .failure: await MainActor.run { self.feed = [] self.hasError = true } } } func getClasses() async { let result = await feedUseCase.getClasses() switch result { case .success(let classes): await MainActor.run { if self.classFeed != classes { self.classFeed = classes } } case .failure: await MainActor.run { self.classFeed = [] self.hasError = true } } }
2. 优化.task的触发逻辑
当前.task仅在视图首次出现时执行,切换isFeedSelected时不会重新加载数据,且无法取消旧任务。绑定isFeedSelected作为task的id,确保状态切换时自动取消旧任务并执行新任务:
.task(id: isFeedSelected) { if isFeedSelected { await viewModel.getFeed() } else { await viewModel.getClasses() } }
3. 确保数据模型遵循Identifiable
ForEach需要高效跟踪列表元素,若模型未遵循Identifiable,会导致频繁的视图重建。给模型添加唯一标识:
// 示例:让FeedItemEntity遵循Identifiable struct FeedItemEntity: Identifiable { let id: String // 或UUID、Int等唯一标识字段 // 其他属性 } // 如果已有唯一字段,可通过扩展实现 extension ClassItemEntity: Identifiable { var id: String { userName } // 假设userName是唯一的,替换为实际唯一字段 }
4. 优化列表项视图的刷新逻辑
让PostItemView遵循Equatable,确保仅当数据变化时才刷新:
struct PostItemView: View, Equatable { let item: FeedItemEntity static func == (lhs: PostItemView, rhs: PostItemView) -> Bool { // 仅比较影响UI的字段,减少不必要的刷新 lhs.item.id == rhs.item.id } var body: some View { // 视图内容 } }
然后在ForEach中使用.equatable()修饰符:
ForEach(viewModel.feed) { post in PostItemView(item: post) .equatable() }
5. 避免Mock Service阻塞主线程
Data(contentsOf:)是同步IO操作,若JSON文件较大,会阻塞主线程导致卡顿。将同步操作移到后台线程:
func getFeed() async throws -> [FeedItemEntity] { return try await Task.detached(priority: .background) { guard let url = Bundle(for: MockedFeedService.self).url(forResource: "basic-test-feed-response", withExtension: "json") else { throw APIServiceError.badUrl } let data = try Data(contentsOf: url) return try JSONDecoder().decode([FeedItemEntity].self, from: data) }.value }
6. 替换过时的NavigationView(可选)
在iOS 16+中,NavigationView已被NavigationStack替代,后者性能更优:
NavigationStack { // 原ScrollView内容 }
内容的提问来源于stack exchange,提问作者Seb
相关产品推荐
相关产品推荐

