学计算机的那个

不是我觉到、悟到,你给不了我,给了也拿不住;只有我觉到、悟到,才有可能做到,能做到的才是我的.

0%

Combine [译]

通过组合 事件处理操作符 自定义异步事件的处理。

Customize handling of asynchronous events by combining event-processing operators.

OverView

Combine框架提供了一个声明式Swift API,用于处理随时间变化的值。这些值可以表示多种异步事件。Combine声明发布者(publishers)以公开可能随时间变化的值,并声明订阅者(subscribers)以从发布者接收这些值。

  • Publisher协议声明了一种可以随时间传递一系列值的类型。发布者有操作符来处理从上游发布者接收到的值并重新发布它们。

  • 在发布者链的末端,订阅者(Subscriber)在接收到元素时对元素进行操作。发布者仅在订阅者显式请求时才发出值。这使订阅者代码可以控制从所连接的发布者接收事件的速度。

一些Foundation类型通过发布者公开它们的功能,包括TimerNotificationCenterURLSessionCombine还为符合键值观察(KVO)的任何属性提供了一个内置发布者。

您可以combine多个发布者的输出并协调(coordinate)它们的交互。例如,您可以从文本字段的发布者订阅更新,并使用文本执行URL请求。然后,你可以使用另一个发布者来处理响应,并使用它们来更新你的应用。

通过采用Combine,可以集中事件处理代码并消除嵌套闭包(nested closures)和基于约定的回调(convention-based)等麻烦的技术,从而使代码更易于阅读和维护。

Receiving and Handling Events with Combine

OverView

Combine框架为应用程序如何处理事件提供了一种声明式的方法。您可以为给定的事件源创建单个处理链,而不是潜在地实现多个委托回调(multiple delegate callbacks)或完成处理程序闭包(completion handler closures)。链的每一部分都是一个Combine操作符,它对从前一步接收到的元素执行不同的操作。

考虑一个需要根据文本字段的内容过滤表或集合视图的应用程序。在AppKit中,文本字段中的每次击键都会产生一个Notification ,您可以使用Combine订阅该通知。收到通知后,你可以使用operators 更改事件传递的内容和时间,并使用最终结果更新应用程序的用户界面。

Connect a Publisher to a Subscriber

要使用Combine接收文本字段的通知,请访问NotificationCenter的默认实例并调用publisher(for:object:)方法。此调用接受通知名称和希望从中获得通知的源对象(source object),并返回生成通知元素的发布者。

1
2
let pub = NotificationCenter.default
.publisher(for: NSControl.textDidChangeNotification, object: filterField)

您使用Subscriber 从发布者接收元素。订阅者定义一个关联类型Input来声明它接收的类型。发布者还定义了一个类型Output来声明它生成的内容。发布者和订阅者都定义了一个类型Failure,以指示它们产生或接收的错误类型。要将订阅者连接到生产者,Output必须匹配Input, Failure类型也必须匹配。

Combine提供了两个内置订阅者,它们自动匹配其附加的发布者的输出(Output)和失败(Failure)类型:

  • sink(receivcompletion: receivvalue:)接受两个闭包。第一个闭包在接收到Subscribers.Completion时执行。这是一个枚举,指示发布者是正常完成还是失败并出现错误。第二个闭包在从发布者接收到元素时执行。

  • assign(to:on:) 立即将它接收到的每个元素分配给给定对象的一个属性,使用一个键路径来指示该属性。

例如,您可以使用接收器订阅者(sink subscriber)记录发布者何时完成,以及每次它接收到元素的时间:

1
2
3
4
let sub = NotificationCenter.default
.publisher(for: NSControl.textDidChangeNotification, object: filterField)
.sink(receiveCompletion: { print ($0) },
receiveValue: { print ($0) })

sink(receiveCompletion:receiveValue:) assign(to:on:)订阅者都向发布者请求无限数量的元素。要控制接收元素的速率,请通过实现Subscriber协议创建自己的订阅者。

Change the Output Type with Operators

上一节中的 sink subscriberreceiveValue 闭包中执行其所有工作。如果需要对接收到的元素执行大量自定义工作或维护调用之间的状态,那么这可能会很麻烦(burdensome)。Combine的优势来自于组合操作符(combining operators)来定制事件交付。

例如,如果您只需要文本字段的字符串值,那么NotificationCenter.Publisher.Output就不是一个方便的回调类型。由于发布者的输出本质上是一个随时间变化的元素序列,因此Combine提供了map(_:)flatMap(maxPublishers:_:)reduce(_:_:)等序列修改操作符。这些操作符的行为类似于Swift标准库中的等价操作符。

要更改发布者的输出类型,可以添加map(_:)操作符,其闭包返回不同的类型。在这种情况下,你可以获得通知的对象作为一个NSTextField,然后获得字段的stringValue

1
2
3
4
5
let sub = NotificationCenter.default
.publisher(for: NSControl.textDidChangeNotification, object: filterField)
.map( { ($0.object as! NSTextField).stringValue } )
.sink(receiveCompletion: { print ($0) },
receiveValue: { print ($0) })

在发布者链生成您想要的类型之后,将sink(receiveCompletion:receiveValue:)替换为assign(to:on:)。下面的示例从发布者链接收字符串,并将它们分配给自定义视图模型对象的filterString:

1
2
3
4
let sub = NotificationCenter.default
.publisher(for: NSControl.textDidChangeNotification, object: filterField)
.map( { ($0.object as! NSTextField).stringValue } )
.assign(to: \MyViewModel.filterString, on: myViewModel)

Customize Publishers with Operators

您可以使用一个操作符扩展Publisher实例,该操作符执行原本需要手动编写的操作。这里有三种使用操作符来改进事件处理链的方法:

  • 您可以使用filter(_:)操作符来忽略特定长度以下的输入或拒绝非字母数字字符,而不是使用输入到文本字段中的任何字符串来更新视图模型。

  • 如果filtering operation开销很大——例如,查询一个大型数据库——您可能希望等待用户停止输入。为此,debounce(For:scheduler:options:)操作符允许您设置发布者发出事件之前必须经过的最小时间段。RunLoop类为以秒或毫秒为单位指定时间延迟提供了便利。

  • 如果结果更新UI,您可以通过调用receive(on:options:)方法向主线程交付回调。通过指定RunLoop类提供的Scheduler实例作为第一个参数,可以告诉Combine在主运行循环中调用订阅者。

生成的发布者声明如下:

1
2
3
4
5
6
7
let sub = NotificationCenter.default
.publisher(for: NSControl.textDidChangeNotification, object: filterField)
.map( { ($0.object as! NSTextField).stringValue } )
.filter( { $0.unicodeScalars.allSatisfy({CharacterSet.alphanumerics.contains($0)}) } )
.debounce(for: .milliseconds(500), scheduler: RunLoop.main)
.receive(on: RunLoop.main)
.assign(to:\MyViewModel.filterString, on: myViewModel)

Cancel Publishing when Desired

发布者将继续发出元素,直到正常完成或失败。如果不再想订阅发布者,可以取消订阅。由sink(receiveCompletion:receiveValue:)assign(to:on:)创建的订户类型都实现了Cancellable协议,该协议提供了一个cancel()方法:

sub?.cancel()

如果创建自定义Subscriber,则在首次订阅时,发布者将发送Subscription对象。存储此subscription ,然后在需要取消发布时调用其cancel()方法。当您创建自定义subscriber 时,您应该实现Cancellable协议,并让cancel()实现将调用转发给存储的subscription

参考

Combine