Apple原生Rx框架Combine简介

Combine是什么

a declarative Swift API for processing values over time.

Combine是苹果推出的函数式Rective编程框架,和RxSwift,ReactiveObjC类似,主要用于处理时间变化的数据或者事件流。Apple的SDK中Combine是一个独立的Framework但是在许多其他的库中都有Combine的支持,比如SwiftUI就大量应用了Combine,其他的一些基础功能比如,NSNotificationCenter,URLSession和Timer也都有Combine的支持

Combine基本概念

函数用来返回一个值,Combine返回可能的多个值(基于时间序列)
函数返回错误或者抛出异常,Combine返回失败
Combine中有两种基本返回,正常输出和失败

Publisher,Subscriber

发布者(Publisher)的角色就是提供输出(output),当有值或者被请求就输出值,如果一个Publisher没有任何请求则被优化不做任何处理,Combine中提供两种基本输出,正常输出(output type)和失败(Failure)
与发布者(Publisher)对应的是订阅者(Subscriber),订阅者(Subscriber)订阅发布者的输出并进行处理。发布者(Publisher)和订阅者(Subcriber)构成Combine的核心概念。和Publisher对应的,Subscriber有一个输入类型(Input Type)和失败(Failure),关系如下图:

Apple原生Rx框架Combine简介_第1张图片
input_output.png

仅仅是Publisher和Subcriber还不够,我们可能需要对数据进行一定的处理再交给下一个,这就引入了另外一个概念,operator,operator同时支持了publisher和Subcriber协议,通过operator你可以订阅一个Publisher,接受它的输出处理后重新发布给其他Subcriber,组合起来就是这样:


Apple原生Rx框架Combine简介_第2张图片
image.png

操作者通常用来做数据类型变换,同时作用于输出类型和失败类型,操作者也可以用于分割输出,复制输出或者组合多个流。操作者的一个限制是输入和输出连接需要对齐,就像一个管道一样,所有连接处都必须一致。我们称这种组合方式为管道(Pipeline)

一个简单的示例:

import Combine
let _ = Just(5) (1)
.map { value -> String in  (2)
    // do something with the incoming value here
    // and return a string
    return "a string"
}
.sink { receivedValue in (3)
    // sink is the subscriber and terminates the pipeline
    print("The end result was \(receivedValue)")
}

(1) 管道起始于发布者Just,输出是5 类型,失败类型是
(2) 管道通过了map操作,返回了一个字符串,输出类型为
(3) subcriber订阅了map后的输出,并打印了result。

发布者和订阅者的生命周期

Combine的设计是让终端操作可以完全控制数据流和管道处理流程,这是一种 back-pressure设计(可以理解为末端控制整个流程),这也意味着subscriber驱动订阅或者operator应该怎么输出数据。subscriber的请求通过通道(Pipeline)逐级向上链条传递。一个典型的例子就是cancel,当Subcriber要求取消的时候可中断链条上的所有操作。

Apple原生Rx框架Combine简介_第3张图片
image.png

也就是subscriber控制整个链条的生命周期

典型的Publisher

  • Just
    返回单个值并立即结束Publisher

  • Future
    创建一个异步的最终返回单个值或失败Publisher

  • @Published
    swift 语法糖,允许将任意属性转换为一个Publisher

  • Empty
    不返回任何值,然后直接结束<可选>

  • Sequence 将容器类型转变为值发布

  • Fail

    • 发送一次值更新,然后立即因错误而终止
    • 立即因错误而终止
  • Deferred
    Deferred 初始化需要提供一个生成 Publisher 的 closure,只有在有 Subscriber 订阅的时候才会生成指定的 Publisher,并且每个 Subscriber 获得的 Publisher 都是全新的。

  • ObservableObjectPublisher

Operators

  • scan
    scan 对每个信号都进行处理
     let pub = (0...5)
         .publisher
         .scan(0, { return $0 + $1 })
         .sink(receiveValue: { print ("\($0)", terminator: " ") })
      // Prints "0 1 3 6 10 15 ".
  • tryScan
    和上面类似,允许抛出错误
  • map
    经常使用,用来转换数据类型
    map还可以用来处理多个属性值拆分处理
    如果输出的value有多个属性,那么也可以用map和keypath来拆分:
[(x: 2, y: 3), (x: 1, y: 5), (x: 2, y: 6)].publisher
    .map(\.x, \.y)
    .sink(receiveCompletion: { print($0) },
        receiveValue: { x, y in print(x + y) })
    .store(in: &subscriptions)
5
6
8
finished
  • tryMap 和tryScan类似
  • flatMap
    如果你有一个publisher发送的value的某些属性也是publisher, 那sink只会receive外面这个publisher发出的value,属性publisher发出的value要是也想接收,那你就可以用flatMap
.flatMap { data in
    return Just(data)
    .decode(YourType.self, JSONDecoder())
    .catch {
        return Just(YourType.placeholder)
    }
}

flatmap常用于异常处理,在我们处理信号时,有时候部分信号代表错误,而你希望做catch处理后给一个默认值就很有用了,像上面一样。

  • reduce
    与scan不同的时,publisher一定要结束才会输出最后值
[1, 2, 3].publisher
    .reduce(0, +)
    .sink(receiveCompletion: { print($0) },
        receiveValue: { print($0) })
    .store(in: &subscriptions)

6
finished
  • filter 筛选出过滤的信号
[1, 2, 3].publisher
    .filter { $0 < 2 }
    .sink(receiveCompletion: { print($0) },
        receiveValue: { print($0) })
    .store(in: &subscriptions)

1
finished

类似的还有fist,last,drop,dropFirst, prefix

Subscriber

Combine 内置的 Subscriber 有三种:

  • Sink
    通用的closure方式处理Publisher的值
let once: Publishers.Once = Publishers.Once(100)
let observer: Subscribers.Sink> = Subscribers.Sink(receiveCompletion: {
    print("completed: \($0)")
}, receiveValue: {
    print("received value: \($0)")
})
once.subscribe(observer)

// received value: 100
// completed: finished
  • Assign
    Assign 可以很方便地将接收到的值通过 KeyPath 设置到指定的 Class 上(不支持 Struct),很适合将已有的程序改造成Reactive。
    例如:
class Student {
    let name: String
    var score: Int

    init(name: String, score: Int) {
        self.name = name
        self.score = score
    }
}

let student = Student(name: "Jack", score: 90)
print(student.score)
let observer = Subscribers.Assign(object: student, keyPath: \Student.score)
let publisher = PassthroughSubject()
publisher.subscribe(observer)
publisher.send(91)
print(student.score)
publisher.send(100)
print(student.score)

// 90
// 91
// 100

一单Publisher发送新的值,Student的score属性也会随着发生变化。

  • Subject
    有些时候我们想随时在 Publisher 插入值来通知订阅者,在 Combine 中也提供了一个 Subject 类型来实现。Subject 通常是一个中间代理,即可以作为 Publisher,也可以作为 Observer。Subject要求实现一个send方法,允许向combine链条发送值。
    Combine有两个内置的Subject类型 CurrentValueSubjectPassthroughSubject
    CurrentValueSubject 的功能很简单,就是包含一个初始值,并且会在每次值变化的时候发送一个消息,这个值会被保存,可以很方便的用来替代 Property Observer。
// Before
class ContentManager {

    var content: [String] {
        didSet {
            delegate?.contentDidChange(content)
        }
    }

    func changeContent() {
        content = ["hello", "world"]
    }
}

// After
class RxContentController {

    var content = CurrentValueSubject<[String], NSError>([])

    func changeContent() {
        content.value = ["hello", "world"]
    }
}

PassthroughSubject 和 CurrentValueSubject 几乎一样,只是没有初始值,也不会保存任何值。

你可能感兴趣的:(Apple原生Rx框架Combine简介)