Combine: core concepts

As mentioned in the previous overview , Combine has three core concepts: Publisher, Operator and Subscriber. Publisher and Subscriber represent event publishers and subscribers, respectively. Operator has the characteristics of both. It follows the Subscriber and Publisher protocols and is used to operate upstream data.

As long as you understand these three core concepts, you can use Combine very well, so from this perspective, we can simply understand Combine as the following form:

Combine = Publishers + Operators + Subscribers

Publisher

definition:

public protocol Publisher<Output, Failure> {

    associatedtype Output
    associatedtype Failure : Error

    func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input
}
复制代码

After the publisher is subscribed, it will provide data according to the subscriber's request. A publisher without any subscriber will not send any data.

Publisher can publish three kinds of events:

  • Output: the new value appearing in the event stream
  • Finished: All elements in the event flow have been released, and the event flow has completed its mission and terminated
  • Failure: An error occurred in the event flow, and the event flow ends here

Finished and Failure events are defined in Subscribers.Completion

extension Subscribers {
    @frozen public enum Completion<Failure> where Failure : Error {
        case finished
        case failure(Failure)
    }
}
复制代码

None of the three events are required. A Publisher may or may not emit one or more output values; it may never stop, or it may signal termination by emitting a failure or finished event .

A stream of events that will eventually terminate is called a finite stream of events, while a stream of events that does not emit failure or finished is called an infinite stream of events. For example, a network request is a finite event stream, and a button click event stream is an infinite event stream.

Subject

Subject is also a Publisher

public protocol Subject : AnyObject, Publisher {
	func send(_ value: Self.Output)
	func send(completion: Subscribers.Completion<Self.Failure>)
}
复制代码

Subject 暴露了两个 send 方法,外部调用者可以通过这两个方法来主动地发布 output 值、failure 事件或 finished 事件。Subject可以将传统的指令式编程中的异步事件和信号转换到响应式的世界中去

Combine内置了两种Subject类型:

  • PassthroughSubject

    简单地将 send 接收到的事件转发给下游的其他 Publisher 或 Subscriber,不会持有最新的output;如果在订阅前执行send操作,是无效的。

let publisher1 = PassthroughSubject<Int, Never>()
print("开始订阅")
publisher1.sink(
	receiveCompletion: { complete in
		print(complete)
	},
	receiveValue: { value in
		print(value)
    })
publisher1.send(1)
publisher1.send(2)
publisher1.send(completion: .finished)
// 输出:
// 开始订阅
// 1
// 2
// finished
复制代码

调整一下 sink 订阅的时机,将它延后到 publisher.send(1) 之后,那么订阅者将会从 2 的事件开始进行响应:

let publisher2 = PassthroughSubject<Int, Never>()
publisher2.send(1)
print("开始订阅")
publisher2.sink(
	receiveCompletion: { complete in
		print(complete)
	},
	receiveValue: { value in
		print(value)
              })
publisher2.send(2)
publisher2.send(completion: .finished)
// 输出:
// 开始订阅
// 2
// finished
复制代码
  • CurrentValueSubject

    会包装和持有一个值,并在设置该值时发送事件并保留新的值。在订阅发生的瞬间,会把当前保存的值发送给订阅者;接下来对值的每次设置都将触发订阅响应。

let publisher3 = CurrentValueSubject<Int, Never>(0)
print("开始订阅")
publisher3.sink(
	receiveCompletion: { complete in
		print(complete)
	},
	receiveValue: { value in
		print(value)
    })
publisher3.value = 1
publisher3.value = 2
publisher3.send(completion: .finished)
// 输出:
// 开始订阅
// 0
// 1
// 2
// finished
复制代码

Subscriber

定义:

public protocol Subscriber<Input, Failure> : CustomCombineIdentifierConvertible {

    associatedtype Input
    associatedtype Failure : Error

    func receive(subscription: Subscription)

    func receive(_ input: Self.Input) -> Subscribers.Demand

    func receive(completion: Subscribers.Completion<Self.Failure>)
}
复制代码

想要订阅某个 Publisher,Subscriber 中的这两个类型必须与 Publisher 的 Output 和 Failure 一致。

Combine 中也定义了几个比较常见的 Subscriber,可以供我们直接使用。

sink

sink的完整函数签名为

func sink(receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable
复制代码

receiveCompletion 用来接收 failure 或者 finished 事件,receiveValue 用来接收 output 值。

let just = Just("Hello word!")
    _ = just.sink(receiveCompletion: {
        print("Received completion", $0)
    }, receiveValue: {
        print("Received value", $0)
    })
复制代码

如果说Subject提供了一条从指令式异步编程通向响应式世界的道路的话,那么sink就补全了另外一侧。sink可以作为响应式代码和基于闭包的指令式代码之间的桥梁,让你可以通过它从响应式的世界中回到指令式的世界。因为receiveValue闭包会将值带给你,想要对它做什么就随你愿意了。

assign

func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root,Self.Output>, on object:Root) -> AnyCancellable
复制代码

assign 接受一个 class 对象以及对象类型上的某个键路径 (key path)。每当 output 事件到来时,其中包含的值就将被设置到对应的属性上去。

定义一个MyObject类

class MyObject {
	var value: String = "123" {
		didSet {
			print(value)
		}
	}
}
复制代码

使用 assign(to:on:)修改MyObject实例对象属性的值

let obj = MyObject()
let _ = ["456"].publisher.assign(to: \.value, on: obj)
复制代码

assign 还有一个变体, assign(to:) 可将 Publisher 发出的值用于 @Published 属性包装器包装过的属性

class MyObject {
	@Published var value = 0
}

let objc = MyObject()
objc.$value.sink {		
	print($0)
}

(0 ..< 5).publisher.assign(to: &objc.$value)
复制代码

value 属性用 @Published包装,除了可作为常规属性访问之外,它还为属性创建了一个 Publisher。使用 @Published 属性上的 $ 前缀来访问其底层 Publisher,订阅该 Publisher,并打印出收到的每个值。最后,我们创建一个 0..<5 的 Int Publisher 并将它发出的每个值 assign 给 object 的 value Publisher。 使用 & 来表示对属性的 inout 引用,这里的 inout 来源于函数签名:

func assign(to published: inout Published<Self.Output>.Publisher)
复制代码

这里有一个值得注意的地方,如果使用 assign(to: .value, on: self) 并存储生成的 AnyCancellable,可能会引起引用循环:MyObject 类实例持有生成的 AnyCancellable,而生成的 AnyCancellable 同样保持对 MyObject 类实例的引用。因此,推荐使用 assign(to:) 来替代 assign(to:on:) ,以避免此问题的发生,因为assign(to:) ::不返回 AnyCancellable,在内部完成了生命周期的管理,在 @Published 属性释放时会取消订阅。::

Operator

关于Operator的介绍,在概览中已经做了相对详细的介绍。

Operators can be used as input to upstream Publishers, and they can also become new Publishers that output processed data to downstream. We can combine different operators to form a processing chain: when the Publisher at the top of the chain publishes events or data, the Operators in the chain will process these data and events step by step, and finally reach the result specified by the subscriber.

Regarding the commonly used operators, this article is very comprehensive and can be used as a reference.

reference

Combine: Asynchronous Programming with Swift

SwiftUI and Combine programming

Guess you like

Origin juejin.im/post/7200653914818691131
Recommended