jch*_*tel 14 swift combine opaque-types
我只是在学习如何使用Combine。我有使用 Rx(RxSwift 和 RxJava)的经验,我注意到它非常相似。
然而,完全不同(有点烦人)的一件事是Publisher协议不对其Output和Failure类型使用泛型;它使用关联类型代替。
这意味着我无法指定多态Publisher类型(例如Publisher<Int, Error>)并简单地返回符合Publisher这些类型的任何类型。我需要AnyPublisher<Int, Error>改用,我被迫在eraseToAnyPublisher()所有地方都包括在内。
如果这是唯一的选择,那么我会忍受它。但是,我最近还了解了 Swift 中的不透明类型,我想知道是否可以使用它们来解决这个问题。
有没有办法,让我有,比方说,一个函数,返回some Publisher和使用的具体类型Output和Failure?
这似乎是不透明类型的完美案例,但我不知道是否有办法既使用不透明类型又指定关联类型。
我正在想象这样的事情:
func createPublisher() -> some Publisher where Output = Int, Failure = Error {
return Just(1)
}
Run Code Online (Sandbox Code Playgroud)
rob*_*off 21
在撰写本文时,Swift 还没有您想要的功能。Joe Groff 在他的“改进泛型 UI”文档的标题为“函数返回缺少类型级抽象”的部分中具体描述了缺失的内容:
但是,通常希望从调用者那里抽象出由实现选择的返回类型。例如,一个函数可能会生成一个集合,但不想透露它到底是什么类型的集合的细节。这可能是因为实现者希望保留在未来版本中更改集合类型的权利,或者因为实现使用组合
lazy转换并且不想在其接口中公开长、脆弱、令人困惑的返回类型。一开始,人们可能会尝试在这种情况下使用存在词:Run Code Online (Sandbox Code Playgroud)func evenValues<C: Collection>(in collection: C) -> Collection where C.Element == Int { return collection.lazy.filter { $0 % 2 == 0 } }但是 Swift 今天会告诉你,这
Collection只能用作通用约束,导致有人自然而然地尝试这样做:Run Code Online (Sandbox Code Playgroud)func evenValues<C: Collection, Output: Collection>(in collection: C) -> Output where C.Element == Int, Output.Element == Int { return collection.lazy.filter { $0 % 2 == 0 } }但这也不起作用,因为如上所述,
Output泛型参数是由调用者选择的——这个函数签名声称能够返回调用者要求的任何类型的集合,而不是由调用者使用的一种特定类型的集合实施。
有可能some Publisher有一天不透明的返回类型语法 ( ) 将被扩展以支持这种使用。
今天你有三个选择。为了理解它们,让我们考虑一个具体的例子。假设您想从 URL 中获取一个整数文本列表,每行一个,并将每个整数作为单独的输出发布:
return dataTaskPublisher(for: url)
.mapError { $0 as Error }
.flatMap { data, response in
(response as? HTTPURLResponse)?.statusCode == 200
? Result.success(data).publisher
: Result.failure(URLError(.resourceUnavailable)).publisher
}
.compactMap { String(data: $0, encoding: .utf8) }
.map { data in
data
.split(separator: "\n")
.compactMap { Int($0) }
}
.flatMap { $0.publisher.mapError { $0 as Error } }
Run Code Online (Sandbox Code Playgroud)
您可以使用完整的、复杂的返回类型。它看起来像这样:
extension URLSession {
func ints(from url: URL) -> Publishers.FlatMap<
Publishers.MapError<
Publishers.Sequence<[Int], Never>,
Error
>,
Publishers.CompactMap<
Publishers.FlatMap<
Result<Data, Error>.Publisher,
Publishers.MapError<
URLSession.DataTaskPublisher,
Error
>
>,
[Int]
>
> {
return dataTaskPublisher(for: url)
... blah blah blah ...
.flatMap { $0.publisher.mapError { $0 as Error } }
}
}
Run Code Online (Sandbox Code Playgroud)
我自己没有弄清楚返回类型。我将返回类型设置为Int,然后编译器告诉我这Int不是正确的返回类型,并且错误消息包括正确的返回类型。这不太好,如果您更改实现,则必须找出新的返回类型。
AnyPublisher添加.eraseToAnyPublisher()到发布者的末尾:
extension URLSession {
func ints(from url: URL) -> AnyPublisher<Int, Error> {
return dataTaskPublisher(for: url)
... blah blah blah ...
.flatMap { $0.publisher.mapError { $0 as Error } }
.eraseToAnyPublisher()
}
}
Run Code Online (Sandbox Code Playgroud)
这是常见且简单的解决方案,通常也是您想要的。如果您不喜欢拼写eraseToAnyPublisher,您可以编写自己的Publisher扩展名来使用较短的名称,如下所示:
extension Publisher {
var typeErased: AnyPublisher<Output, Failure> { eraseToAnyPublisher() }
}
Run Code Online (Sandbox Code Playgroud)
Publisher类型您可以用自己的类型包装您的发布者。您的类型receive(subscriber:)构造“真正的”发布者,然后将订阅者传递给它,如下所示:
extension URLSession {
func ints(from url: URL) -> IntListPublisher {
return .init(session: self, url: url)
}
}
struct IntListPublisher: Publisher {
typealias Output = Int
typealias Failure = Error
let session: URLSession
let url: URL
func receive<S: Subscriber>(subscriber: S) where
S.Failure == Self.Failure, S.Input == Self.Output
{
session.dataTaskPublisher(for: url)
.flatMap { $0.publisher.mapError { $0 as Error } }
... blah blah blah ...
.subscribe(subscriber)
}
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
4368 次 |
| 最近记录: |