我试图理解合并和SwiftUI如何与MVVM和clean架构结合使用,但是我在使用withAnimation
时遇到了一个问题,因为我的视图模型有一个更新发布值的async方法。我能够解决这个问题,但是我很确定这不是正确的方法,我遗漏了一些基本的东西。下面是我的解决方案,从我的数据管理器开始:
protocol NameManaging {
var publisher: AnyPublisher<[Name], Never> { get }
func fetchNames() async
}
class MockNameManager: NameManaging {
var publisher: AnyPublisher<[Name], Never> {
names.eraseToAnyPublisher()
}
func fetchNames() async {
var values = await heavyAsyncTask()
names.value.append(contentsOf: values)
}
private func heavyAsyncTask() async -> [Name] {
// do some heavy async task
}
private var names = CurrentValueSubject<[Name], Never>([])
}
然后查看模型:
class NameListViewModel: ObservableObject {
@Published var names = [Name]()
private var anyCancellable: AnyCancellable?
private var nameManager: NameManaging
init(nameManager: NameManaging = MockNameManager()) {
self.nameManager = nameManager
self.anyCancellable = nameManager.publisher
.receive(on: DispatchQueue.main)
.sink(receiveValue: { [weak self] values in
withAnimation {
self?.names = values
}
})
}
func fetchNames() async {
await nameManager.fetchNames()
}
}
最后,我的观点是:
struct NameList: View {
@StateObject private var nameListViewModel = NameListViewModel()
var body: some View {
VStack {
VStack {
HStack {
Button(action: updateNames) {
Text("Fetch some more names")
}
}
}
.padding()
List {
ForEach(nameListViewModel.names) {
NameRow(name: $0)
}
}
}
.navigationTitle("Names list")
.onAppear(perform: updateNames)
}
func updateNames() {
Task {
await nameListViewModel.fetchNames()
}
}
}
我所做的是在数据管理器发布器的.sink()
方法中使用视图模型中的withAnimation
。这和预期的一样,但是它在视图模型中引入了视图函数。我如何才能在视图中的updateNames
中使用withAnimation
呢?或者我应该用完全不同的方式来做?
1条答案
按热度按时间pxy2qtax1#
您正在混淆技术。async/await的要点是消除对状态对象的需要(即引用类型)和合并来执行异步操作。您可以简单地使用
.task
修饰符来调用任何异步函数,并在@State var
上设置结果。如果异步函数引发了异常,则您可能会捕获该异常,并在另一个@State var
上设置消息。.task
的好处是当UIView(SwiftUI为您创建的)出现时调用它,当UIView消失时取消它(可选的id
参数也改变了)。因此不需要对象,这通常是导致一致性/内存问题的原因,Swift和SwiftUI使用值类型旨在消除这些问题。