Github.com/agelessman/…

Subscribers’ role in Combine is that of a subscriber and is dedicated to receiving data.

assign

class AssignViewObservableObject: ObservableObject {
    var cancellable: AnyCancellable?
    
    @Published var text = ""
    
    init(a) {
        let publisher = PassthroughSubject<String.Never>()
        
        cancellable = publisher
            .assign(to: \.text, on: self)
        
        publisher.send("Hello, world")}}Copy the code
public func assign<Root> (to keyPath: ReferenceWritableKeyPath<Root.Self.Output>, on object: Root) -> AnyCancellable
Copy the code

The first parameter of assign is of type ReferenceWritableKeyPath. ReferenceWritableKeyPath Requires that the parameter be a reference type and writable keypath.

Keypath is a very powerful technique in Swift. Swift is known to be strongly typed, and it is only keypath that allows us to access properties of a certain type.

Keypath is not the main content of this article, but its usage is discussed briefly. There are three main types of keypath:

  • KeyPath: Can access only data
  • WritableKeyPath: Can access and write data
  • ReferenceWritableKeyPath: Can only access and write data of reference type

For example, we have a student model:

struct Student {
    let name: String
    let age: Int
    let score: Int
}
Copy the code

If we want to filter out the name,age, or score code, it looks something like this:

let students = [Student(name: "Zhang", age: 20, score: 80)]

print(students.map { $0.name })
print(students.map { $0.age })
print(students.map { $0.score })
Copy the code

Next, we can use KeyPath’s dark magic as follows:

extension Sequence {
    func map<T> (_ keyPath: KeyPath<Element.T>)- > [T] {
        map { $0[keyPath: keyPath] }
    }
}
Copy the code
print(students.map(\.name))
print(students.map(\.age))
print(students.map(\.score))
Copy the code

This is just a simple example. The real power of KeyPath is in the ability to generalize. I won’t explain more about KeyPath here.

In short, Assign uses the power of KeyPath, but it can’t accept errors.

sink

public func sink(receiveValue: @escaping ((Self.Output) - >Void)) -> AnyCancellable
Copy the code
public func sink(receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure- > >)Void), receiveValue: @escaping ((Self.Output) - >Void)) -> AnyCancellable
Copy the code

Compared with assign, sink is an all-powerful subscriber, which can be used in two ways:

  • When publisher’s error type isNever, you can use the simple form, you can pass only onereceiveValueClosure to receive data sent by Publisher
  • When publisher is of another type of error, you need to use a complex situation, passing in two closure parameters,receiveCompletionUsed to receive completion events including.finishedand.failure.receiveValueUsed to receive data
class SinkViewObservableObject: ObservableObject {
    var cancellable: AnyCancellable?
    
    @Published var text = ""
    
    init(a) {
        let publisher = PassthroughSubject<String.Never>()
        
        cancellable = publisher
            .sink(receiveValue: { value in
                self.text = value
            })
        
        publisher.send("Hello, world")}}Copy the code

The code above is equivalent to the code below:

class SinkViewObservableObject: ObservableObject {
    var cancellable: AnyCancellable?
    
    @Published var text = ""
    
    init(a) {
        let publisher = PassthroughSubject<String.Never>()
        
        cancellable = publisher
            .assign(to: \.text, on: self)
        
        publisher.send("Hello, world")}}Copy the code

When publisher calls sink, it returns an AnyCancellable type. The core method of this protocol is:

final public func cancel(a)
Copy the code

Calling this method cancels the pipline that is currently executing. In short, sink is the subscriber we usually use most in development.

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")}}Copy the code

OnReceive is a subscriber used in SwiftUI, which is a method of View protocol. That is to say, as long as the View protocol is implemented, this method can be called:

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

}
Copy the code

The above example is an example of timer, since it is not used much in SwiftUI, we will not explain in detail.

AnyCancellable

public func assign<Root> (to keyPath: ReferenceWritableKeyPath<Root.Self.Output>, on object: Root) -> AnyCancellable
Copy the code
public func sink(receiveCompletion: @escaping ((Subscribers.Completion<Self.Failure- > >)Void), receiveValue: @escaping ((Self.Output) - >Void)) -> AnyCancellable
Copy the code

Assign and sink, both of which return types are AnyCancellable, allow us to call.cancel() to cancel the current pipline, so we usually use an attribute to refer to this return value so that we can cancel the pipline if necessary.

class AnyCancellableViewObservableObject: ObservableObject {
    var cancellable1: AnyCancellable
    init(a) {
        let publisher = PassthroughSubject<String.Never>()
        
        cancellable1 = publisher
            .sink(receiveValue: { print($0)}}}Copy the code

If multiple pipelines are used in an implementation of ObservableObject, they can be saved into a collection:

class AnyCancellableViewObservableObject: ObservableObject {
    var cancellables: Set<AnyCancellable> = []
    
    init(a) {
        let publisher = PassthroughSubject<String.Never>()
        
        publisher
            .sink(receiveValue: { print($0) })
            .store(in: &cancellables)
    }
}
Copy the code