如何在SwiftUI中链接AnyPublisher和async/await



链接AnyPublisher的典型方法是使用像flatMap这样的Combine操作符。


class MyService {
func getUserList() -> AnyPublisher<[User], Error> {
....
}
func getPostList(user: User) -> AnyPublisher<[Post], Error> {
...
}
}
class ViewModel: ObserableObject {
let service = MyService()

@published var post: [Post] = []

func fetchAllPostFromLastUser() {
service.getUserList().flatMap { [weak self] users in
if let user = users.last {
return self.service.getPosts(user: user)
} else {
return Fail(error:APIError.emptyUsers).eraseToAnyPublisher()
}
}
.sink { result in

}
}
}

是否有更优雅的方法来使用async/await,因此代码可以类似于

class ViewModel: ObserableObject {
let service = MyService()

@published var post: [Post] = []

func fetchAllPostFromLastUser() {
let users = await service.getUserList().somethingMagicToConvertPublisherToAsync()
let posts = await service.getPostList(user: user.first).somethingMagicToConvertPublisherToAsync()
}
}

要使用async/await,我们需要将Publisher转换为AsyncStream的值,从而消除错误。

所以,它有可能像(用Xcode 13.4测试)

@MainActor class ViewModel: ObserableObject {
// ...
func collectPosts() async {
for await newUsers in service.getUserList().replaceError(with: []).values {
for user in newUsers {
for await newPost in service.getPost(user: user).replaceError(with: []).values {
post.append(contentsOf: newPost)
}
}
}
}

和类似

的用法
struct ContentView: View {
@StateObject var vm = ViewModel()
var body: some View {
YourViewHere()
.task {
await vm.collectPosts()
}
}
}

add anypublisher扩展名:

enum AsyncError: Error {
case finishedWithoutValue
}
extension AnyPublisher {
func async() async throws -> Output {
try await withCheckedThrowingContinuation { continuation in
var cancellable: AnyCancellable?
var finishedWithoutValue = true
cancellable = first()
.sink { result in
switch result {
case .finished:
if finishedWithoutValue {
continuation.resume(throwing: AsyncError.finishedWithoutValue)
}
case let .failure(error):
continuation.resume(throwing: error)
}
cancellable?.cancel()
} receiveValue: { value in
finishedWithoutValue = false
continuation.resume(with: .success(value))
}
}
}
}

和使用like so

let todo = try await api.loadTodo().async()

更多信息在这里https://medium.com/geekculture/from-combine-to-async-await-c08bf1d15b77

相关内容

  • 没有找到相关文章

最新更新