我的请求导致AnyPublisher
let foo: AnyPublisher<API.Response, API.Error> = APIClient.get(API.Endpoints.Demo)
现在我想将这个发布者的值映射到字符串数组,API中的message
。响应类型是[String : [String]]
,所以我的尝试是
foo.map { $0.message.map { $0.key } }
在这一点上,我希望我最终会与AnyPublisher<[String], API.Error>
,或只是任何其他出版商,但参数化为<[String], API.Error>
,
而不是我最终与Publishers.Map<AnyPublisher<API.Response, API.Error>, [String]>
。
func map<T>(_ transform: @escaping (Self.Output) -> T) -> Publishers.Map<Self, T>
上面的.map
函数的文档是正确的,但为什么它是这样工作的-为什么有人期望映射保持以前的出版商类型,但以某种方式嵌套在那里。我实在想不明白,请帮助我,谢谢:)
如果您习惯了其他reactivex风格的库,或者您习惯了对象总是堆分配的语言(如Java或Kotlin或Scala),那么组合类型可能会相当令人震惊。
由于Publishers.Map
对其上游发布器的类型(在本例中为AnyPublisher<API.Response, API.Error>
)是泛型的,因此它可以内联存储其上游发布器,而不需要将上游封装在存在的容器中,也不需要为上游堆分配存储空间。
Map
还优化了map
操作符的连续使用。Combine框架为所有Publisher
定义了一个map
操作符,Map
继承了它。但是Map
还定义了一个更特殊的map
运算符,它将连续的map
运算符合并在一起。
下面是一个例子:
import Combine
let foo: AnyPublisher<[String: [String]], Error> = Empty().eraseToAnyPublisher()
let chained = foo
.map { Array($0.keys) }
.filter { !$0.isEmpty }
.map { $0.joined(separator: ", ") }
.map { "[($0)]" }
print("chained", type(of: chained))
chained
的类型是什么?这是
Map<
Filter<
Map<
AnyPublisher<
Dictionary<String, Array<String>>,
Error
>,
Array<String>
>
>,
String
>
注意这里的两件事。尽管我使用了三次map
运算符,但该类型中只有两个Map
。这是因为map
的最终使用使用了Map
类型的专用map
运算符,该运算符将连续两次使用的map
合并为单个Map
实例。
由于Map
和Filter
是struct
s,它们可以存储在堆栈中,也可以作为其他数据类型的属性直接内联。因此,chained
中的值可以存储在堆栈中。
一般来说,Map
可能需要在堆上存储它的transform
函数,但它也不需要执行堆分配来存储它的上游。在上面的例子中,由于传递给map
和filter
的参数都没有关闭任何外部变量,Swift可能能够优化出所有
chained
值时的堆分配。在这里使用泛型类型还可以让编译器更多地了解chained
值是如何构造的,因此它可能能够执行其他优化。
因此,对发布者应用操作符通常会返回不同类型的发布者。如果您需要编写一个以发布者为参数的函数,这意味着您应该使该函数在发布者的类型上具有泛型。例如,每个SwiftUIView
都有一个onReceive
修饰符,签名如下:
func onReceive<P>(
_ publisher: P,
perform action: @escaping (P.Output) -> Void
) -> some View where P : Publisher, P.Failure == Never
您可以将任何发布者传递给onReceive
,只要该发布者的Failure
关联类型是Never
。因此,您可以将Map
或Filter
或AnyPublisher
传递给onReceive
。