如何在 SwiftUI 中使用 async/await 链接 AnyPublisher

问题描述 投票:0回答:2

链接 AnyPublisher 的典型方法是使用诸如 flatMap 之类的组合运算符。


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()
    }
}

swiftui async-await combine
2个回答
5
投票

添加

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))
                }
        }
    }
}

并像这样使用

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

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


3
投票

要使用

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()
            }
    }
}
© www.soinside.com 2019 - 2024. All rights reserved.