iOS Swift Combine: Emit Publisher with single value
iOS Swift Combine: Emit Publisher with single value
我正在使用 Combine,我多次遇到需要发出具有单一值的 Publisher 的情况。
例如,当我使用平面地图并且我必须 return 一个具有单个值作为错误或单个对象的 Publisher 时,我使用了这段代码,它工作得很好:
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()
对象,但我不明白如何使用,因为现阶段的文档非常不清楚。
我们可以做的主要事情是在所有地方使用 .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: [=10=]) }) else {
return Result.Publisher(nil).eraseToAnyPublisher()
}
我们可以用mapError
代替catch
。 catch
运算符是通用的:只要 Success
类型匹配,您就可以从中 return 任何 Publisher
。但在你的情况下,你只是丢弃传入的失败并 returning 一个持续的失败,所以 mapError
更简单:
return NetworkService.getData(url: url)
.mapError { _ in .cantDownloadProfileImage }
我们可以在这里使用点快捷方式,因为这是 return
语句的一部分。因为它是 return
语句的一部分,所以 Swift 推断出 mapError
转换必须 return 一个 StoreError
。所以它知道去哪里寻找 .cantDownloadProfileImage
.
的含义
flatMap
运算符需要转换为 return 固定的 Publisher
类型,但它不必 return AnyPublisher
。因为您在 flatMap
之外的所有路径中都使用 Result<UIImage?, StoreError>.Publisher
,所以不需要将它们包装在 AnyPublisher
中。事实上,如果我们将转换更改为使用 Optional
的 map
方法而不是 guard
,我们根本不需要指定转换的 return 类型声明:
.flatMap({ data in
UIImage(data: data)
.map { Result.Publisher([=12=]) }
?? Result.Publisher(.cantDownloadProfileImage)
})
.eraseToAnyPublisher()
同样,这是 return
声明的一部分。这意味着 Swift 可以为我们推导出 Result.Publisher
的 Output
和 Failure
类型。
另请注意,我在转换闭包周围放置了圆括号,因为这样做会使 Xcode 正确缩进右大括号,以便与 .flatMap
对齐。如果您不将闭包括在括号中,则 Xcode 会将右括号与 return
关键字对齐。呃.
全部都在这里:
func downloadUserProfilePhoto(user: User) -> AnyPublisher<UIImage?, StoreError> {
guard let url = user.imageURL.flatMap({ URL(string: [=13=]) }) else {
return Result.Publisher(nil).eraseToAnyPublisher()
}
return NetworkService.getData(url: url)
.mapError { _ in .cantDownloadProfileImage }
.flatMap({ data in
UIImage(data: data)
.map { Result.Publisher([=13=]) }
?? 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([=10=])
}.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([=10=])
})
unendingPublisher.send(5)
在上面的代码片段中,我正在将一个 passthroughsubject 发布者转换成一个在发送第一个值后停止的东西。基于WWDCsession关于结合https://developer.apple.com/videos/play/wwdc2019/721/介绍的精华片段在这里
我们基本上强制在 tryMap 中抛出一个错误,然后使用 Just 解析发布者捕获它,正如问题所述,在订阅第一个值后将完成。
理想情况下,订阅者可以更好地表明需求。
另一个稍微古怪的替代方法是在发布者上使用 first 运算符
let subscription_with_first = unendingPublisher.first().sink(receiveCompletion: { _ in
print("subscription with first ended")
}, receiveValue: {
print([=11=])
})
我正在使用 Combine,我多次遇到需要发出具有单一值的 Publisher 的情况。
例如,当我使用平面地图并且我必须 return 一个具有单个值作为错误或单个对象的 Publisher 时,我使用了这段代码,它工作得很好:
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()
对象,但我不明白如何使用,因为现阶段的文档非常不清楚。
我们可以做的主要事情是在所有地方使用 .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: [=10=]) }) else {
return Result.Publisher(nil).eraseToAnyPublisher()
}
我们可以用mapError
代替catch
。 catch
运算符是通用的:只要 Success
类型匹配,您就可以从中 return 任何 Publisher
。但在你的情况下,你只是丢弃传入的失败并 returning 一个持续的失败,所以 mapError
更简单:
return NetworkService.getData(url: url)
.mapError { _ in .cantDownloadProfileImage }
我们可以在这里使用点快捷方式,因为这是 return
语句的一部分。因为它是 return
语句的一部分,所以 Swift 推断出 mapError
转换必须 return 一个 StoreError
。所以它知道去哪里寻找 .cantDownloadProfileImage
.
flatMap
运算符需要转换为 return 固定的 Publisher
类型,但它不必 return AnyPublisher
。因为您在 flatMap
之外的所有路径中都使用 Result<UIImage?, StoreError>.Publisher
,所以不需要将它们包装在 AnyPublisher
中。事实上,如果我们将转换更改为使用 Optional
的 map
方法而不是 guard
,我们根本不需要指定转换的 return 类型声明:
.flatMap({ data in
UIImage(data: data)
.map { Result.Publisher([=12=]) }
?? Result.Publisher(.cantDownloadProfileImage)
})
.eraseToAnyPublisher()
同样,这是 return
声明的一部分。这意味着 Swift 可以为我们推导出 Result.Publisher
的 Output
和 Failure
类型。
另请注意,我在转换闭包周围放置了圆括号,因为这样做会使 Xcode 正确缩进右大括号,以便与 .flatMap
对齐。如果您不将闭包括在括号中,则 Xcode 会将右括号与 return
关键字对齐。呃.
全部都在这里:
func downloadUserProfilePhoto(user: User) -> AnyPublisher<UIImage?, StoreError> {
guard let url = user.imageURL.flatMap({ URL(string: [=13=]) }) else {
return Result.Publisher(nil).eraseToAnyPublisher()
}
return NetworkService.getData(url: url)
.mapError { _ in .cantDownloadProfileImage }
.flatMap({ data in
UIImage(data: data)
.map { Result.Publisher([=13=]) }
?? 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([=10=])
}.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([=10=])
})
unendingPublisher.send(5)
在上面的代码片段中,我正在将一个 passthroughsubject 发布者转换成一个在发送第一个值后停止的东西。基于WWDCsession关于结合https://developer.apple.com/videos/play/wwdc2019/721/介绍的精华片段在这里
我们基本上强制在 tryMap 中抛出一个错误,然后使用 Just 解析发布者捕获它,正如问题所述,在订阅第一个值后将完成。
理想情况下,订阅者可以更好地表明需求。
另一个稍微古怪的替代方法是在发布者上使用 first 运算符
let subscription_with_first = unendingPublisher.first().sink(receiveCompletion: { _ in
print("subscription with first ended")
}, receiveValue: {
print([=11=])
})