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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 10:39:57