ios MVVM中的异步更新和状态更改动画

wbgh16ku  于 2022-11-26  发布在  iOS
关注(0)|答案(1)|浏览(152)

我试图理解合并和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呢?或者我应该用完全不同的方式来做?

pxy2qtax

pxy2qtax1#

您正在混淆技术。async/await的要点是消除对状态对象的需要(即引用类型)和合并来执行异步操作。您可以简单地使用.task修饰符来调用任何异步函数,并在@State var上设置结果。如果异步函数引发了异常,则您可能会捕获该异常,并在另一个@State var上设置消息。.task的好处是当UIView(SwiftUI为您创建的)出现时调用它,当UIView消失时取消它(可选的id参数也改变了)。因此不需要对象,这通常是导致一致性/内存问题的原因,Swift和SwiftUI使用值类型旨在消除这些问题。

struct NameList: View {
    @State var var names: [Name] = []
    @State var fetchCount = 0

    var body: some View {
        VStack {
            VStack {
                HStack {
                    Button("Fetch some more names") {
                        fetchCount += 1
                    }
                }
            }
            .padding()

            List {
                ForEach(names) { name in
                    NameRow(name: name)
                }
            }
        }
        .navigationTitle("Names list")
        .task(id: fetchCount) {
            let names = await Name.fetchNames()
            withAnimation {
                self.names = names
            }
        }
    }
}

相关问题