我正在学习如何使用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)
}
答案 0 :(得分:12)
在撰写本文时,Swift没有您想要的功能。乔·格罗夫(Joe Groff)在他的“Improving the UI of generics” document的标题为“函数返回缺少类型级抽象”的节中专门描述了什么:
但是,通常要提取由选择的返回类型 实施。例如,一个函数可能会产生 集合,但不想透露具体的种类 的收藏。这可能是因为实施者想要 保留在将来的版本中更改集合类型的权利,或者 因为该实现使用组合的
lazy
转换,并且没有 想要在其类型中公开一个长的,易碎的,令人困惑的返回类型 接口。最初,人们可能会尝试在其中使用存在性 情况:func evenValues<C: Collection>(in collection: C) -> Collection where C.Element == Int { return collection.lazy.filter { $0 % 2 == 0 } }
但是Swift今天会告诉您
Collection
只能用作 通用约束,导致某人自然尝试以下方法: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
)以支持这种用法。
所以您今天有两个选择:
Publishers.FlatMap<Publishers.CombineMany<Publishers.Just<etc etc>>>
)。AnyPublisher
并退回。通常,您选择第二个选项,因为它更容易读写。但是,有时您会看到使用第一个选项的方法。例如,Combine自己的combineLatest
运算符具有一个变体,该变体采用闭包来转换组合的值,并返回Publishers.Map<Publishers.CombineLatest<Self, P>, T>
而不是擦除为AnyPublisher<T, Failure>
。
如果您不喜欢在各地拼写eraseToAnyPublisher
,可以给它起一个简短的名字:
extension Publisher {
var erased: AnyPublisher<Output, Failure> { eraseToAnyPublisher() }
}
答案 1 :(得分:2)
对于不透明的返回,其类型由闭包的确切返回值定义,因此您可以只使用
func createPublisher() -> some Publisher {
return Just(1)
}
let cancellable = createPublisher()
.print()
.sink(receiveCompletion: { _ in
print(">> done")
}) { value in
print(">> \(value)")
}
// ... all other code here
,并且有效。经过Xcode 11.4的测试。
答案 2 :(得分:0)
我对some Publisher
没有任何运气(讨厌的限制)。
一种选择是使用AnyPublisher
:
func a() -> AnyPublisher<(a: Int, b: String), Never> {
return Just((a: 1, b: "two")).eraseToAnyPublisher()
}
func b() -> AnyPublisher<String, Never> {
return a().map(\.b).eraseToAnyPublisher()
}
a().sink(receiveValue: {
let x = $0 // (a: 1, b: "two)
})
b().sink(receiveValue: {
let x = $0 // "two"
})
或者,“ Apple方式”(它们在标准库中使用的方式)似乎是类型别名(或包装器结构):
enum PublisherUtils {
typealias A = Just<(a: Int, b: String)>
typealias B = Publishers.MapKeyPath<A, String>
// or implement a simple wrapper struct like what Combine does
}
func a() -> PublisherUtils.A {
return Just((a: 1, b: "two"))
}
func b() -> PublisherUtils.B {
return a().map(\.b)
}
a().sink(receiveValue: {
let x = $0 // (a: 1, b: "two)
})
b().sink(receiveValue: {
let x = $0 // "two"
})
这是Combine框架中Publishers
命名空间的目的。
结构比类型别名更不透明。类型别名可能会导致出现类似Cannot convert Utils.MyTypeAlias (aka 'TheLongUnderlyingTypeOf') to expected type ABC
之类的错误消息,因此,最接近正确的不透明类型的可能是使用结构,这实际上就是AnyPublisher
的含义。