在 2019 年的 WWDC 大会上,Combine 框架登场,它是苹果公司新推出的“响应式”框架,用来处理随时间变化的事件。你可以用 Combine 来统一和简化像代理、通知、定时器、完成回调这样的代码。在 iOS 平台上,之前也有可用的第三方响应式框架,但现在苹果开发了自己的框架。
- 1.什么是Combine,为啥学习Combine
- 2.Combine的基本概念
- 3.目标 -解决异步事件处理的一般流程和推荐方法
1.什么是Combine
Customize handling of asynchronous events by
combining event-processing operators.
通过 组合 事件处理运算符 来处理自定义异步事件
是官方的框架,不依赖其他三方库,由苹果长期维护。swiftUI的UI模型,及更新机制也是有combine来支持的。
2.Combine的基本概念
Combine框架 发布者 Publisher 操作符 Operator 订阅 Subscription 订阅者 Subscribers
2.1.请看Publisher定义
Failure 2种 1.Never 2.Error finished failure(failure)
Publisher 最主要的工作其实有两个:
- 发布新的事件及其数据,
- 以及准备好被 Subscriber 订阅。
Output 定义了某个 Publisher 所发布的值的类型,Failure 则定义可能产生的错误的类型。随着时间的推移,事件流也会逐渐向前发展。对应 Output 及 Failure,Publisher 可以发布三种事件: 类型为 Output 的新值:这代表事件流中出现了新的值。 类型为 Failure 的错误:这代表事件流中发生了问题,事件流到此终止。 完成事件:表示事件流中所有的元素都已经发布结束,事件流到此终止。”
“虽然 Publisher 可以发布三种事件,但是它们并不是必须的。一个 Publisher 可能发出一个或多个 output 值,也可能一个值都不发出;Publisher 有可能永远不会停止终结,也有可能通过 failure 或者 finished 事件来表明不再会发出新的事件。我们将最终会终结的事件流称为有限事件流,而将不会发出 failure 或者 finished 的事件流称为无限事件流。”
有限事件流 -
最常见的一个例子是网络请求的相关操作:发出网络请求后,可以把每次接收到数据的事件看作一个 output。在请求的所有数据都被返回时,整个操作正常结束,finished 事件被发布。如果在过程中遭遇到错误,比如网络连接断开或者连接被服务器关闭等,则发布 failure 事件。不论是 finished 或者是 failure,都表明这次请求已经完成,将来不会有更多的 output 发生。”
无限事件流
则正好相反,这类 Publisher 永远不会发出 failure 或者 finished。一个典型的例子是 UI 操作,比如用户点击某个按钮的事件流:如果将按钮看作是事件的发布者,每次按钮点击将发布一个不带有任何数据的 output。这种按钮操作并没有严格意义上的完结:用户可能一次都没有点击这个按钮,也可能无限次地点击这个按钮,不论用户如何操作,你都无法断言之后不会再发生任何按钮事件。”
思考问题
发布者:
什么时候,发布什么数据,发送多少次,可以发送错误信息吗?停止发送的条件。
操作符
操作什么类型的数据,处理成什么什么类型的数据呢?
订阅者
订阅什么类型的数据(异常数据、正常数据,结束),订阅的数据的数量,
3.目标 -解决异步事件处理的一般流程和推荐方法
发布者 Publisher
系统提供常用的发布者
** 发布一次** Just(value),创建一个发布Value的发布者,发送一次就结束。 Future(closure) 创建1个执行入参的 (closure)的发布者。 运行完closure ,并把数据传递给订阅者。并结束
订阅者
@available(OSX 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public protocol Subscriber : CustomCombineIdentifierConvertible {
/// 这个订阅者要接收的值的类型
associatedtype Input
/// 这个订阅者可能接收到的错误的类型
///
/// 如果这个订阅者不会接收到错误,使用 `Never`
associatedtype Failure : Error
/// 告知订阅者成功订阅了发布者,并且可以获取发布项
///
/// 使用收到的 `Subscription` 来向发布者请求内容
/// - Parameter subscription: 订阅,代表发布者和订阅者之间的连接
func receive(subscription: Subscription)
/// 告知订阅者,发布者已经发布了一个元素
///
/// - Parameter input: 发布了的元素
/// - Returns: 命令,指明订阅者还期望接收多少元素
func receive(_ input: Self.Input) -> Subscribers.Demand
/// 告知订阅者,发布者已经结束了发布,可能是正常结束,也可能是因为发生了错误
///
/// - Parameter completion: 完成,指明发布结束是正常结束还是由于错误而结束
func receive(completion: Subscribers.Completion<Self.Failure>)
}
Subscription(订阅) 的源码:
public protocol Subscription : Cancellable, CustomCombineIdentifierConvertible {
/// 通知发布者,它可以向订阅者发送一个或多个值 func request(_ demand: Subscribers.Demand) }
例子
class OfficialDemo {
class MyViewModel {
var filterString = ""
}
private let filterField = UITextField()
private let myViewModel = MyViewModel()
private var subscription: AnyCancellable?
func bind() {
subscription = NotificationCenter.default .publisher(for: UITextField.textDidChangeNotification, object: filterField) .
map( { (($0.object as! UITextField).text ?? "") } )
.filter( { $0.unicodeScalars.allSatisfy({CharacterSet.alphanumerics.contains($0)}) } )
.debounce(for: .milliseconds(500), scheduler: RunLoop.main)
.receive(on: RunLoop.main)
.assign(to:\MyViewModel.filterString, on: myViewModel) } }
bind 方法中的代码完成了很多任务:
- 通过通知中心来订阅输入框
filterField的textDidChangeNotification通知; - 通过
map将接收到的内容转换为输入框中的文本; - 通过
filter来过滤掉无效的文本内容,阻止内容继续沿着订阅链往后传递; - 通过
debounce(for:scheduler:)来控制内容往后传递的频率(收到内容之后的500毫秒后执行后续操作,如果在这个时间段内收到了新内容,则重新计时500毫秒再执行后续操作); - 通过
receive(on:)来指定执行后续操作的调度器(线程); - 通过
assign(to:on:)来使用 keypath 为指定的对象赋值(每次收到内容就会执行一次);
如果不使用 Combine,上面这一系列的任务可能需要写非常多的代码才能完成。而且代码的组织结构将会变得很庞大,你可能需要写很多方法来封装这些操作。