Combine之Subscribers

1,162 阅读3分钟

github.com/agelessman/…

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最核心的功能就是它第一个参数的类型是ReferenceWritableKeyPathReferenceWritableKeyPath要求该参数必须是引用类型且可写的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.failurereceiveValue用于接收数据
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

本文上边讲解的assignsink,他们的返回值类型都是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)
    }
}