什么是响应式编程?
简单的理解就是:仅当事件发生以后才响应处理。
c <= a + b + c...
以上表达式中,c
是响应处理的逻辑,a
, b
, c
这些都是事件,当它们发生以后就会触发执行 c
。
使用术语表达,c
叫订阅者(Subscriber
);a
, b
, c
这些都叫发布者(Publisher
);
另外还有对事件进行处理的逻辑,比如进行变换、去重等等,叫操作符(Operator
)。
完整的响应框架就由这三部分组成:订阅者(Subscriber
),发布者(Publisher
)和操作符(Operator
)。
举个例子
UI 事件总是在发生着,比如 UIView
中的安全区域会随着屏幕方向不同而变化,程序里需要根据安全区域的变化来调整布局。
使用响应式编程的思想来处理,就是订阅事件然后进行处理:
safeAreaSubscriber = view.publisher(for: \.safeAreaInsets).removeDuplicates().sink {
print($0)
}
sink
就是订阅者,负责响应处理逻辑,返回的是类型是 AnyCancellable
,如果需要取消订阅就调用它的 cancel()
方法。
在 Combine 框架中,订阅者是需要保存起来的,否则就会被自动释放。
removeDuplicates()
就是操作符,它的作用是对事件进行去重,保证订阅者每次得到的数据都是不同的。
publisher(for: \.safeAreaInsets)
很明显就是发布者了,\.safeAreaInsets
是写法是 KeyPath 表达式:\{type-name}.{path}
。
使用 Combine 把命令式改造成函数式
申请相册访问权限的一般方式:
PHPhotoLibrary.requestAuthorization { status in
DispatchQueue.main.async {
if status == .authorized {
// 访问相册的逻辑代码
} else {
// 提示用户没有权限访问相册
}
}
}
改为响应式的方式,把相册的申请调用调整为返回一个发布者类型:
enum PhotosAuthError: Error {
case notAuthorized(PHAuthorizationStatus)
}
func authorizeAccessPhotos() -> AnyPublisher<PHAuthorizationStatus, PhotosAuthError> {
return Future { promise in
PHPhotoLibrary.requestAuthorization { status in
if status == .authorized {
promise(.success(status))
} else {
promise(.failure(PhotosAuthError.notAuthorized(status)))
}
}
}.eraseToAnyPublisher()
}
使用的时候:
private var cancellables = Set<AnyCancellable>()
authorizeAccessPhotos().receive(on: DispatchQueue.main)
.sink(receiveCompletion: {
// 没有访问权限
if case .failure(let error) = $0 {
if case .notAuthorized(let status) = error {
print(status)
}
}
}, receiveValue: {
// 获取到了访问权限
print($0)
})
.store(in: &cancellables)
代码比原来更多了,但是会带来一些额外的好处:对事件可以添加更多的控制逻辑。
与观察者模式的差异
KVO、KVC 是观察者模式的典型代表,也是监听变更然后进行逻辑处理。
观察者模式不能对事件进行变换、筛选等操作,没有操作符。
响应式编程是把事件抽象为时间流上的点,订阅者订阅这个时间流上发生的事件,然后进行汇总处理。
其它第三方响应式编程库
因为 Combine
框架是 iOS 13 才提供的,而且是为了配合 SwiftUI 而诞生的。
在这之前已经有了很多第三方开源库实现了响应式编程框架。虽然每个库里订阅者(Subscriber
),发布者(Publisher
)和操作符(Operator
)
有不同的名称,但是设计模式是一致的,只要稍加学习就可以很快上手使用。