Subscribers在Combine中的角色是订阅者,专门用于接收数据。
assign
class AssignViewObservableObject: ObservableObject {
var cancellable: AnyCancellable?
@Published var text = ""
init() {
let publisher = PassthroughSubject<String, Never>()
cancellable = publisher
.assign(to: \.text, on: self)
publisher.send("Hello, world")
}
}
public func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable
从上边的代码可以看出,assign
最核心的功能就是它第一个参数的类型是ReferenceWritableKeyPath
。ReferenceWritableKeyPath
要求该参数必须是引用类型且可写的keypath。
在Swift中,keypath是一项非常强大的技术,大家都知道Swift是强类型的,只有通过keypath,我们才能访问到某个类型的属性。
keypath不是本文的主要内容,我们简单讲一下它的用法,它主要分为3种类型:
KeyPath
: 只能访问数据WritableKeyPath
:既可以访问,又能写入数据ReferenceWritableKeyPath
:只能访问和写入引用类型的数据
比方说,我们有一个学生模型:
struct Student {
let name: String
let age: Int
let score: Int
}
我们如果想筛选出name
,age
或者score
代码大概是这样的:
let students = [Student(name: "张三", age: 20, score: 80)]
print(students.map { $0.name })
print(students.map { $0.age })
print(students.map { $0.score })
接下来,我们就可以使用KeyPath的黑魔法了,代码如下:
extension Sequence {
func map<T>(_ keyPath: KeyPath<Element, T>) -> [T] {
map { $0[keyPath: keyPath] }
}
}
print(students.map(\.name))
print(students.map(\.age))
print(students.map(\.score))
上边只是一个简单的例子,KeyPath真正的强大之处在于范型能力,这里就不做更多解释了,后续会专门写篇相关用法的文章。
总之一句话,assign使用了KeyPath的强大能力,但它不能接受Error。
sink
public func sink(receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable
public func sink(receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable
sink
相对assign
来说,算是一个全能的subscriber,它有两种使用方法:
- 当publisher的错误类型为
Never
时,可以使用简单形式,可以只传入一个receiveValue
闭包,用于接收publisher发送的数据 - 当publisher的错误类型为其他类型时,需使用复杂形势,需要传入两个闭包参数,
receiveCompletion
用于接收完成事件,包括.finished
和.failure
,receiveValue
用于接收数据
class SinkViewObservableObject: ObservableObject {
var cancellable: AnyCancellable?
@Published var text = ""
init() {
let publisher = PassthroughSubject<String, Never>()
cancellable = publisher
.sink(receiveValue: { value in
self.text = value
})
publisher.send("Hello, world")
}
}
上边的代码与下边的代码等效:
class SinkViewObservableObject: ObservableObject {
var cancellable: AnyCancellable?
@Published var text = ""
init() {
let publisher = PassthroughSubject<String, Never>()
cancellable = publisher
.assign(to: \.text, on: self)
publisher.send("Hello, world")
}
}
当publisher调用了sink
后,返回一个AnyCancellable
类型,该协议的核心方法是:
final public func cancel()
调用该方法就可以取消当前正在执行的pipline。总之,sink是我们平时开发用到最多的subscriber。
onReceive
struct OnReceiveView: View {
private var timer = Timer.TimerPublisher(interval: 1.0,
runLoop: RunLoop.main,
mode: .common).autoconnect()
@State private var count = 0
var body: some View {
Text("\(count)")
.onReceive(timer) { _ in
self.count += 1
}
.navigationBarTitle("onReceive")
}
}
onReceive
是用在SwiftUI内的一个subscriber,它是View协议的一个方法,也就说只要实现了View协议的都可以调用该方法:
extension View {
/// Adds an action to perform when this view detects data emitted by the
/// given publisher.
///
/// - Parameters:
/// - publisher: The publisher to subscribe to.
/// - action: The action to perform when an event is emitted by
/// `publisher`. The event emitted by publisher is passed as a
/// parameter to `action`.
///
/// - Returns: A view that triggers `action` when `publisher` emits an
/// event.
@inlinable public func onReceive<P>(_ publisher: P, perform action: @escaping (P.Output) -> Void) -> some View where P : Publisher, P.Failure == Never
}
上边的例子就是一个定时器的例子,由于在SwiftUI中用的不是很多,我们就不详细讲解了。
AnyCancellable
public func assign<Root>(to keyPath: ReferenceWritableKeyPath<Root, Self.Output>, on object: Root) -> AnyCancellable
public func sink(receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure>) -> Void), receiveValue: @escaping ((Self.Output) -> Void)) -> AnyCancellable
本文上边讲解的assign
和sink
,他们的返回值类型都是AnyCancellable
,它允许我们调用.cancel()
函数来取消当前的pipline,所以,通常我们都使用一个属性来指向这个返回值,以便在需要的时候取消掉pipline。
class AnyCancellableViewObservableObject: ObservableObject {
var cancellable1: AnyCancellable
init() {
let publisher = PassthroughSubject<String, Never>()
cancellable1 = publisher
.sink(receiveValue: { print($0) })
}
}
如果在ObservableObject的实现中用到了多个pipeline,可以把他们保存到一个集合中:
class AnyCancellableViewObservableObject: ObservableObject {
var cancellables: Set<AnyCancellable> = []
init() {
let publisher = PassthroughSubject<String, Never>()
publisher
.sink(receiveValue: { print($0) })
.store(in: &cancellables)
}
}