簡體   English   中英

iOS Swift 組合:使用單個值發出發布者

[英]iOS Swift Combine: Emit Publisher with single value

我正在使用 Combine,我經常遇到需要發出具有單個值的 Publishers 的情況。

例如,當我使用平面 map 並且我必須返回具有單個值作為錯誤或單個 object 的發布者時,我使用此代碼,並且效果很好:

return AnyPublisher<Data, StoreError>.init(
           Result<Data, StoreError>.Publisher(.cantDownloadProfileImage)
        )

這將創建一個<Data, StoreError>類型的 AnyPublisher 並發出錯誤,在這種情況下: .cantDownloadProfileImage

這是一個完整的例子,如何使用這段代碼。

func downloadUserProfilePhoto(user: User) -> AnyPublisher<UIImage?, StoreError> {
        guard let urlString = user.imageURL,
            let url = URL(string: urlString)
            else {
                return AnyPublisher<UIImage?, StoreError>
                    .init(Result<UIImage?, StoreError>
                        .Publisher(nil))
        }
        return NetworkService.getData(url: url)
            .catch({ (_) -> AnyPublisher<Data, StoreError> in
                return AnyPublisher<Data, StoreError>
                    .init(Result<Data, StoreError>
                        .Publisher(.cantDownloadProfileImage))
            })
            .flatMap { data -> AnyPublisher<UIImage?, StoreError> in
                guard let image = UIImage(data: data) else {
                    return AnyPublisher<UIImage?, StoreError>
                        .init(Result<UIImage?, StoreError>.Publisher(.cantDownloadProfileImage))
                }
                return AnyPublisher<UIImage?, StoreError>
                    .init(Result<UIImage?, StoreError>.Publisher(image))
        }
        .eraseToAnyPublisher()
    }

是否有一種更簡單、更短的方法來創建內部具有單個值的 AnyPublisher?

我想我應該以某種方式使用Just() object,但我不明白如何,因為現階段的文檔非常不清楚。

我們可以做的收緊代碼的主要方法是在任何地方使用.eraseToAnyPublisher()而不是AnyPublisher.init 這是我對您的代碼唯一真正的挑剔。 使用AnyPublisher.init不是慣用的,而且令人困惑,因為它添加了一層額外的嵌套括號。

除此之外,我們還可以做更多的事情。 請注意,您編寫的內容(除了未正確使用.eraseToAnyPublisher()之外)很好,尤其是對於早期版本。 以下建議是我在通過編譯器獲得更詳細的版本會做的事情。

我們可以使用OptionalflatMap方法將user.imageURL轉換為 URL。 我們也可以讓 Swift 推斷Result類型參數,因為我們在return語句中使用Result ,所以 Swift 知道預期的類型。 因此:

func downloadUserProfilePhoto(user: User) -> AnyPublisher<UIImage?, StoreError> {
    guard let url = user.imageURL.flatMap({ URL(string: $0) }) else {
        return Result.Publisher(nil).eraseToAnyPublisher()
    }

我們可以使用mapError代替catch catch運算符是通用的:只要Success類型匹配,您就可以從中返回任何Publisher 但在你的情況下,你只是丟棄傳入的失敗並返回一個持續的失敗,所以mapError更簡單:

    return NetworkService.getData(url: url)
        .mapError { _ in .cantDownloadProfileImage }

我們可以在這里使用點快捷方式,因為這是return語句的一部分。 因為它是return語句的一部分,所以 Swift 推斷mapError轉換必須返回一個StoreError 所以它知道在哪里尋找.cantDownloadProfileImage的含義。

flatMap運算符要求轉換返回一個固定的Publisher類型,但它不必返回AnyPublisher 因為您在flatMap之外的所有路徑中使用Result<UIImage?, StoreError>.Publisher ,所以您不需要將它們包裝在AnyPublisher中。 實際上,如果我們將轉換更改為使用Optionalmap方法而不是guard語句,我們根本不需要指定轉換的返回類型:

        .flatMap({ data in
            UIImage(data: data)
                .map { Result.Publisher($0) }
                ?? Result.Publisher(.cantDownloadProfileImage)
        })
        .eraseToAnyPublisher()

同樣,這是return語句的一部分。 這意味着 Swift 可以為我們推導出Result.PublisherOutputFailure類型。

另請注意,我在 transform 閉包周圍加上括號,因為這樣做會使 Xcode 正確縮進右大括號,以與.flatMap 如果您不將閉包包裹在括號中,則 Xcode 將使用return關鍵字排列右大括號。 啊。

這一切都在一起:

func downloadUserProfilePhoto(user: User) -> AnyPublisher<UIImage?, StoreError> {
    guard let url = user.imageURL.flatMap({ URL(string: $0) }) else {
        return Result.Publisher(nil).eraseToAnyPublisher()
    }
    return NetworkService.getData(url: url)
        .mapError { _ in .cantDownloadProfileImage }
        .flatMap({ data in
            UIImage(data: data)
                .map { Result.Publisher($0) }
                ?? Result.Publisher(.cantDownloadProfileImage)
        })
        .eraseToAnyPublisher()
}
import Foundation
import Combine

enum AnyError<O>: Error {
    case forcedError(O)
}

extension Publisher where Failure == Never {
    public var limitedToSingleResponse: AnyPublisher<Output, Never> {
        self.tryMap {
            throw AnyError.forcedError($0)
        }.catch { error -> AnyPublisher<Output, Never> in
            guard let anyError = error as? AnyError<Output> else {
                preconditionFailure("only these errors are expected")
            }
            switch anyError {
            case let .forcedError(publishedValue):
                return Just(publishedValue).eraseToAnyPublisher()
            }
        }.eraseToAnyPublisher()
    }
}

let unendingPublisher = PassthroughSubject<Int, Never>()

let singleResultPublisher = unendingPublisher.limitedToSingleResponse

let subscription = singleResultPublisher.sink(receiveCompletion: { _ in
    print("subscription ended")
}, receiveValue: {
    print($0)
})

unendingPublisher.send(5)




在上面的代碼片段中,我將一個傳遞主題發布者轉換為可以發送 stream 值的內容,該發布者在發送第一個值后停止。 基於WWDC session 的精華片段介紹結合https://developer.apple.com/videos/play/wwdc2019/721/這里。

我們本質上是強制在 tryMap 中拋出一個錯誤,然后使用 Just 將其捕獲到解析發布者,因為問題狀態將在訂閱第一個值后完成。

理想情況下,訂戶更好地指示需求。

另一個稍微古怪的選擇是在發布者上使用第一個運算符

let subscription_with_first = unendingPublisher.first().sink(receiveCompletion: { _ in
    print("subscription with first ended")
}, receiveValue: {
    print($0)
})

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM