提问人:Seb 提问时间:11/14/2023 最后编辑:Seb 更新时间:11/14/2023 访问量:139
swift Task/Async 未在后台运行并阻塞主线程
swift Task/Async not running in background and blocking main thread
问:
我正在尝试创建一个提要,但现在使用来自 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 上,这些调用更复杂,或者我只是遗漏了一些东西。
知道如何使应用程序高效流畅吗?
谢谢
答:
-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 年内没有更新。
评论
@MainActor
Task
@MainActor
Task
getFeed()
FeedViewModel
onAppear
Task {}
Task.detached {}
.onAppear{...}
.task { if(isFeedSelected) { await viewModel.getFeed() } else { await viewModel.getClasses() } }
Task { @MainActor in
getFeed()
getClasses()
@StateObject private var viewModel = FeedViewModel()
MainActor.run
Task.detached
async
func
.task
task(id:selectedTab)