swift Task/Async 未在后台运行并阻塞主线程

swift Task/Async not running in background and blocking main thread

提问人:Seb 提问时间:11/14/2023 最后编辑:Seb 更新时间:11/14/2023 访问量:139

问:

我正在尝试创建一个提要,但现在使用来自 JSON 的模拟数据。但是,它非常慢。

以下是我对提要的看法

struct FeedView: View {
    
    @StateObject private var viewModel = FeedViewModel()
    @State private var isFeedSelected: Bool = true

    var body: some View {
        NavigationView {
            ScrollView {
                LazyVStack {
                    if(isFeedSelected) {
                        ForEach(viewModel.feed) { post in
                            PostItemView(item: post)
                        }
                    } else {
                        ForEach(viewModel.classFeed) { classItem in
                            Text(classItem.userName)
                        }
                    }
                }
            }
            .padding([.top], 10)
            .task {
                if(isFeedSelected) {
                    await viewModel.getFeed()
                } else {
                    await viewModel.getClasses()
                }
            }

ViewModel 如下所示:

final class FeedViewModel: ObservableObject {
    
    var feedUseCase: FeedUseCase

    @Published var feed: [FeedItemEntity] = []
    @Published var classFeed: [ClassItemEntity] = []
    
    @Published var state: FeedState = .na
    
    @Published var hasError: Bool = false

    init() {
        self.feedUseCase = FeedUseCase.shared
        setupErrorSubscription()
    }
    
    func getFeed() async {
        Task.detached {
            let result = await self.feedUseCase.getFeed()
            switch(result) {
            case .success(let feed):
                self.feed = feed
            case .failure(let error):
                self.feed = []
                self.hasError = true
            }
        }
    }
    
    func getClasses() async {
        Task.detached {
            let result = await self.feedUseCase.getClasses()
            switch(result) {
            case .success(let classes):
                self.classFeed = classes
            case .failure(let error):
                self.classFeed = []
                self.hasError = true
            }
        }
        
    }
    
}

用例是这样做的:

enum UseCaseError: Error{
    case networkError, decodingError
}

final class FeedUseCase {
    
    static let shared = FeedUseCase()
    
    let repository: FeedRepositoryProtocol
    
    init() {
        self.repository = FeedRepository.shared
    }
    
    func getFeed() async -> Result<[FeedItemEntity], UseCaseError> {
        do {
            let feed = try await repository.getFeed()
            return .success(feed)
        } catch (let error) {
            switch(error){
                case APIServiceError.decodingError:
                    return .failure(.decodingError)
                default:
                    return .failure(.networkError)
                }
            
        }
        
    }

最后,repo 只是这样做:

func getFeed() async throws -> [FeedItemEntity] {
        return try await service.getFeed()
    }

然后按如下方式提供服务:

func getFeed() async throws -> [FeedItemEntity] {
        guard let url = Bundle(for: MockedFeedService.self).url(forResource: "basic-test-feed-response",withExtension: "json"),
              let data = try? Data(contentsOf: url) else {
            throw APIServiceError.badUrl
        }
            
        guard let result = try? JSONDecoder().decode([FeedItemEntity].self, from: data) else {
            throw APIServiceError.decodingError
        }
        
        return result
    }

据我所知,循环似乎经常被调用,我认为它可能会影响性能。该应用程序非常非常慢。ForEach

我习惯于在 Android 上开发,协程机制通常在后台运行,但似乎在 iOS 上,这些调用更复杂,或者我只是遗漏了一些东西。

知道如何使应用程序高效流畅吗?

谢谢

swift 异步 swiftui async-await

评论

1赞 lazarevzubov 11/14/2023
你发布了很多代码,但引起我注意的是你如何在一些 s 中使用。您知道它的存在是为了确保代码在主线程(也称为 UI)上运行,对吧?@MainActorTask@MainActor
1赞 Timmy 11/14/2023
如果函数的内容包含在 中,则无需将函数标记为异步,请参阅 中的函数。此外,当使用 时,将在主线程上执行的内容,请使用代替。TaskgetFeed()FeedViewModel onAppearTask {}Task.detached {}
0赞 Seb 11/14/2023
@lazarevzubov我不知道。我刚刚开始使用 swift,所以我正在从各种来源收集信息
0赞 workingdog support Ukraine 11/14/2023
或者,您可以尝试 : 并删除 from 和 。也使用.onAppear{...}.task { if(isFeedSelected) { await viewModel.getFeed() } else { await viewModel.getClasses() } }Task { @MainActor in getFeed()getClasses()@StateObject private var viewModel = FeedViewModel()
1赞 lorem ipsum 11/14/2023
不要使用 SwiftUI 的自然流程,并等待调用完成且 UI 更新后转到 Main Actor。也摆脱那些变得无关紧要的东西。 自动取消任何正在运行的任务,您可以使用MainActor.runTask.detachedasyncfunc.tasktask(id:selectedTab)

答:

-3赞 malhal 11/14/2023 #1

由于您使用的是 async/await,因此加快速度的一种方法是删除旧的 Combine ,例如ObservableObject

struct FeedView: View {
    @Environment(\.feedController) var controller // renamed from feedUseCase
    @State private var feed: [FeedItem] = []

    var body: some View {
        ...
        .task { // normally there is an id param for the feed you want to fetch
           do {
               feed = await controller.getFeed()
           catch {
               // it's good practice to set an error message in a state and show it
           }
        }
        ...

如果你让你的控制器不是主要角色,它在后台线程上会更快。将 an 用于控制器的原因是,您可以在预览时将其切换为模拟。EnvironmentKey

评论

0赞 lazarevzubov 11/14/2023
我的问题不是严格批评你的答案,我只是好奇,是什么让你称 Combine 及其财产包装器为遗产?
0赞 malhal 11/14/2023
更改为旧,4 年内没有更新。