Combine框架解读一(原理部分)

864 阅读5分钟

Combine 框架是苹果自己的响应式编程的框架, 至于什么是响应式编程,我这里就不再赘述,可以参考一下 ReactiveSwift RxSwift等知名的框架

言归正传: 直接上Demo: SimpleCombine 别忘了直接给个Star

开源框架 Open

SimpleCombine 是我根据自己的理解拆分的Combine框架, 组织架构不一定完全和Combine一致, 不过Combine的基本流程和思路已经梳理清楚, 阅读起来应该会比较清晰

先上使用案例:

func normalDemo() {
    // 创建发布者
    let up = SimplePassthroughSubject<String, Never>()
    // 自动回收内存的容器, 需要让持有
    var cancellable: [SimpleAnyCancellable] = []
    
    // 订阅1 二者选其一
    up.sink { str in
        print("------\(str)-----")
    }.store(in: &cancellable)

   // 订阅2
   let sub = SimpleSubscribers.Sink<String, Never> { _ in
    } receiveValue: { input in
        print(input)
    }
    sub.store(in: &cancellable)
    up.receive(subscriber: sub)
    // 符号操作
    up.filter { output in
        return output == "王金山"
    }.sink { output in
        print("输出的数据是: " + output)
    }.store(in: &cancellable)
    // 发送数据
    up.send("王金山")
}

下面先看下 架构图

架构图.png

消息流程图

Combine消息流程图.png

一: 架构图类讲解

首先需要记住几个概念: 发布者 订阅者 契约 管道 符号, 后面的所有内容都是按照这几个概念展开的

  • 发布者: 整个消息的起始发布者
  • 订阅者: 接收消息的使用者
  • 契约: 管道能够处理的协议能力,是发布者和订阅者之间进行数据交互需要的一些行为,最终交给管道,在管道内完成
  • 管道: 消息传递,沟通发布者和订阅者的桥梁,消息都是通过管道发送出去的
  • 符号: 其实是特殊的发布者,他负责对发布者的进行一系列的限制措施,比如过滤,跳过发送等等

下面根据我自己实现的简单骨架类来带你起飞 SimpleCombine

1.1 发布者

发布者协议:

// 发布者协议
public protocol SimplePublisher {
    associatedtype Output
    associatedtype Failure: Error
    // 接收订阅者
    func receive<Subscriber: SimpleSubscriber>(subscriber: Subscriber)
        where Failure == Subscriber.Failure, Output == Subscriber.Input
}
 
 // 发布者需要的订阅能力
extension SimplePublisher {
    public func subscribe<Subscriber: SimpleSubscriber>(_ subscriber: Subscriber)
        where Failure == Subscriber.Failure, Output == Subscriber.Input {
        receive(subscriber: subscriber)
    }
}

扩充发布者默认的基枚举

// 所有的发布者
public enum SimplePublishers {}

内置的默认发布者 SimplePassthroughSubject遵守 SimpleSubject 他有几个能力: 1, 接收订阅者:

public func receive<Downstream: SimpleSubscriber>(subscriber: Downstream)
        where Output == Downstream.Input, Failure == Downstream.Failure {}

2, 主送发送消息

public func send(_ input: Output) {}

3, 关联管道,让管道处理数据然后和订阅者数据交互

// 在接收订阅者方法中实现
let conduit = Conduit(parent: self, downstream: subscriber)
subscriber.receive(subscription: conduit)

1.2 订阅者

订阅者都遵守订阅协议:

public protocol SimpleSubscriber: SimpleCombineIdentifierConvertible {
    func receive(subscription: SimpleSubscription)
    func receive(_ input: Input) -> SimpleSubscribers.Demand
    func receive(completion: SimpleSubscribers.Completion<Failure>)
}

扩展订阅的基枚举

public enum SimpleSubscribers {}

内置的两个订阅者 SinkAssign 比如 Skin 具备以下能力 1, 接收发布者发送过来的锲约管道信息

public func receive(subscription: SimpleSubscription) {}

2, 接收管道具体处理完成的数据

public func receive(_ value: Input) -> SimpleSubscribers.Demand {}

1.3 契约

契约其实是一个协议,他主要是让管道去遵守协议,然后发布者把遵守协议的管道发送给订阅者,然后订阅者在收到契约后决定管道处理数据的方案

public protocol SimpleSubscription: SimpleCancellable, SimpleCombineIdentifierConvertible {
    func request(_ demand: SimpleSubscribers.Demand)
}

1.4 管道

管道是处理数据,传递数据的地方,遵守契约协议 他有以下能力 1, 请求数据,然后发送数据给订阅者

override func request(_ demand: SimpleSubscribers.Demand) {
    请求发布者原始数据,回传给订阅者
}

2, 处理发布者主动发送的数据,回传给订阅者

override func offer(_ output: Output) {
    请求发布者的主动数据发送给订阅者
}

1.5 符号

符号主要是处理发布者消息,比如过滤发布者消息等等 遵守发布者协议,实现接收订阅者协议,然后创建自己的管道,让管道去处理数据

public struct Filter<Upstream: SimplePublisher>: SimplePublisher {
public func receive<Subscriber>(subscriber: Subscriber) where Subscriber : SimpleSubscriber, Upstream.Failure == Subscriber.Failure, Upstream.Output == Subscriber.Input {
    upstream.subscribe(Inner(downstream: subscriber, filter: isIncluded)) // Inner 就是自己的管道
    }
}

管道处理符号应该过滤的情况

func receive(_ input: Upstream.Output) -> SimpleSubscribers.Demand {
    if filter(input) { // 判断满足的条件
        return downstream.receive(input)
    }
    return .max(1)
}

概念和概念中主流代码已经简单介绍完毕,下面给出按照Demo梳理的数据流向伪代码 代码流

------- 发布者侧--------
//1 发布者方法订阅
public func sink(receiveValue: @escaping (Output) -> Void) -> SimpleAnyCancellable {
  //2触发发布者的订阅代理
      func receive<Subscriber: SimpleSubscriber>(subscriber: Subscriber)
}

//3 发布者接收订阅者
public func receive<Downstream: SimpleSubscriber>(subscriber: Downstream) {
   // 创建管道
   // 4,把管道发给订阅者
   subscriber.receive(subscription: conduit)
}

------- 订阅者侧--------
// 5, 订阅者收到管道和契约
public func receive(subscription: SimpleSubscription) {
    // 6, 根据契约内容,让契约发起请求数据需要,然后管道处理数据
    subscription.request(.unlimited)
}

------- 管道侧--------
override func request(_ demand: SimpleSubscribers.Demand) {
    // 7, 处理数据,发送数据
}

------- 订阅者侧--------
public func receive(_ value: Input) -> SimpleSubscribers.Demand {
    // 8,接收到管道发来的数据,执行方法回调
}

// 特殊的发布者
发布者执行 
up.send("王金山")
// 发布者侧
public func send(_ input: Output) {
    let downstreams = self.downstreams
       downstreams.forEach { conduit in
          conduit.offer(input)
     }
}
// 管道侧
override func offer(_ output: Output) {
    // 回到 7, 处理数据,发送数据
}

到此Combine的整个框架的基本思路梳理完成, 剩下的就是缝缝补补

二: 扩展 UIKit

思路就是扩展 SimplePublishers 遵守SimplePublisher 创建自定义的管道,在管道中处理数据 比如给 UIButton 加publisher 举例子

extension UIButton {
    var tapPublisher: SimpleAnyPublisher<UIControl, Never> {
        return controlEventPublisher(for: .touchUpInside)
    }
}
public extension UIControl {
    func controlEventPublisher(for events: UIControl.Event) -> SimpleAnyPublisher<UIControl, Never> {
       return SimplePublishers.ControlEvent(control: self, events: events).eraseToAnyPublisher()
    }
}
struct ControlEvent<Control: UIControl>: SimplePublisher {
public func receive<Subscriber: SimpleSubscriber>(subscriber: Subscriber) where Subscriber.Failure == Failure, Subscriber.Input == Output {
    let subscription = CocoSubscription(subscriber: subscriber,control: control,event: controlEvents)
    subscriber.receive(subscription: subscription)
     }
}
private final class CocoSubscription: SimpleSubscription {
    init(subscriber: Subscriber, control: Control, event: Control.Event) {
        control.addTarget(self, action: #selector(handleEvent(control:)), for: event)
}

实战部分在下一篇的内容中!!! Combine API实战