7dd3d9932b86d9f2e43bd869bad5e3f6
Apple 官方异步编程框架:Swift Combine 简介

WWDC 2019 Session 722 - Introducing Combine

引言

在现代 GUI 编程中,开发者会处理大量事件(包括网络,屏幕输入,系统通知等),根据事件去让界面变化。而对异步事件的处理,会让代码和状态变得复杂。而现有的 Cocoa 框架中,异步编程的接口主要有以下这几种:

  • Target/Action
  • NotificationCener
  • KVO
  • Callbacks

而在实际情况中,由于不同的第三方库,系统框架,业务代码可能采用不一样的方式处理异步事件,会导致对事件的处理分散且存在差异。苹果为了帮助开发者简化异步编程,发布了 Swift 的异步编程框架 - Combine。

What is Combine

> "A unified, declarative API for 
processing values over time"

统一、声明式、为处理变化的值而生的 API

Combine 作用是将异步事件通过组合事件处理操作符进行自定义处理。

关注如何处理变化的值,正是响应式编程所考虑的。也可以说,Combine 是一个苹果官方的 Swift 响应式框架。

响应式编程(Reactive Programming):面向异步数据流的编程思想。业界比较知名的响应式框架是 ReactiveX 系列。Rx 也有 Swift 版本的 RxSwift。

Combine 特性

由于 Combine 是一个 Swift 编写的框架,所以 Combine 可以受益于 Swift 的一些语言特性。

泛型支持

Combine 享受 Swift 泛型带来的便利性。泛型可以帮助开发者提取更多模板代码,这也意味着我们可以让异步操作的代码支持泛型,然后适配到各个种类的异步操作中。

类型安全

同样受惠于 Swift,可以让编译器和 Runtime 帮助我们检查类型安全问题。

组合优先

Combine 的主要设计理念,使用组合。组合的优点是可以将核心设计得简单又便于理解,但当放在一起使用时,能产生 1 + 1 > 2 的效果。

请求驱动

请求驱动(Request Driven):基于请求和响应的设计思想,消费者向生产者请求某个事务的变化,当变化时生产者给消费者对应的响应。

事件驱动(Event Driven):基于事件通知的设计思想。在事务发生变化时,生产者将通知提交给事件管道进行分发,而不关心谁去消费事件。消费者需要到事件管道中订阅关心的通知。

Combine 是基于请求和响应的设计思想的,这允许你更精准的控制 App 的内存使用和性能。(这一块苹果没有详细解释)

Combine 核心

Combine 框架有三个核心概念

  • 发布者(Publisher)
  • 订阅者(Subscriber)
  • 操作符(Operator)

发布者(Publisher)

发布者在 Combine 框架中是一个协议:

public protocol Publisher {

    /// 产生的值的类型
    associatedtype Output

    /// 失败的错误类型
    associatedtype Failure : Error

    /// 实现这个方法,将调用 `subscribe(_:)` 订阅的订阅者附加到发布者上
    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

extension Publisher {
    /// 将订阅者附加到发布者上,供外部调用,不直接使用 `receive(_:)` 方法
    public func subscribe<S>(_ subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}

发布者定义了如何描述产生的值和错误,通过定义关联类型 OutputFailure 的实际类型(当发布者不产生错误时,可以使用 Never)。由于发布者不需要实际产生值和错误,所以我们可以用值类型来定义它,也就是 Swift 里的结构体。发布者提供让订阅者注册的能力,通过实现 receive 方法。

发布者可以适配到现有的很多异步操作接口中。

官方 Cocoa 框架中 NotificationCenter 接口适配发布者的例子(截止至 beta1 版本的 Xcode, 此 API 尚未开放beta2 版本已支持):

extension NotificationCenter {
        struct Publisher: Combine.Publisher {
                typealias Output = Notification
                typealias Failure = Never
                init(center: NotificationCenter, name: Notification.Name, object: Any? = nil)
        }
}

订阅者(Subscriber)

和发布者配对的,就是订阅者。同样的,在 Combine 框架中是一个协议:

public protocol Subscriber : CustomCombineIdentifierConvertible {

    /// 接受到的值的类型
    associatedtype Input

    /// 可能接受到的错误的类型
    associatedtype Failure : Error

    /// 告诉订阅者,它在发布者上被成功订阅,可以请求值了
    func receive(subscription: Subscription)

    /// 告诉订阅者,发布者产生值了
    func receive(_ input: Self.Input) -> Subscribers.Demand

    /// 告诉订阅者,发布者已经终止产生值了,不管是正常情况还是由于情况
    func receive(completion: Subscribers.Completion<Self.Failure>)
}

订阅者定义了如何描述接受的值和错误,类似的,通过定义关联类型 InputFailure。由于订阅者在接受到值之后,经常会影响和修改某些状态,所以我们使用引用类型来定义它,也就是 Swift 里的类类型。

订阅者有三个核心方法:

  • 接收到订阅的消息
  • 接收到产生的值的消息
  • 接收到产生已经终止的消息

订阅消息(Subsciption):描述如何控制发布者到订阅者的数据流动,用于表达发布者和订阅者之间的连接。

官方的提供 Assign 的订阅者例子(可以在文档中找到更详细的定义):

extension Subscribers {
    final public class Assign<Root, Input> : Subscriber, Cancellable {

        public typealias Failure = Never

        public init(object: Root, keyPath: ReferenceWritableKeyPath<Root, Input>)
            //...
    }
}
top Created with Sketch.