[英]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()
之外)很好,尤其是對於早期版本。 以下建議是我在通過編譯器獲得更詳細的版本后會做的事情。
我們可以使用Optional
的flatMap
方法將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
中。 實際上,如果我們將轉換更改為使用Optional
的map
方法而不是guard
語句,我們根本不需要指定轉換的返回類型:
.flatMap({ data in
UIImage(data: data)
.map { Result.Publisher($0) }
?? Result.Publisher(.cantDownloadProfileImage)
})
.eraseToAnyPublisher()
同樣,這是return
語句的一部分。 這意味着 Swift 可以為我們推導出Result.Publisher
的Output
和Failure
類型。
另請注意,我在 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.