# 前言
上篇博文中,已经将 RxSwift
替换为了 Combine
,开头也提到了只是简单的使用 Combine
。此篇中作为这两天学习的一个小结。通过对 ViewModel
的改造来进一步学习 Combine
的使用。
-
文中不会过多介绍
Combine
中Publisers
,Subscribers
的类型——官方注释很详细并且上篇中也有相关博客/文章链接。 -
文中以 XTDemo 的
TextureDemoViewController
的改造为例进行说明。 -
效果图:
阅读本文您将得到:
-
使用
Publisher
封装MJRefresh
刷新事件(UIContol
与此类似)。 -
使用
Subscriber
封装MJRefresh
结束刷新的方法 -
ViewModel
的Input
中方法调用改为订阅者(AnySubscriber<Input, Never>
)属性 -
通过操作符创建新的
Publisher
本文中将涉及的方法主要有:
-
Publisher
的func receive<S>(subscriber: S) where S : Subscriber, Self.Failure == S.Failure, Self.Output == S.Input 复制代码
-
AnySubscriber
的@inlinable public init<S>(_ s: S) where Input == S.Input, Failure == S.Failure, S : Subscriber public init<S>(_ s: S) where Input == S.Output, Failure == S.Failure, S : Subject 复制代码
# Publiser 和 Publishers, Subscriber 和 SubScribers
官方注释上我们能很明白的知道:
Publishers
是 发布者(Publisher
) 的命名空间。- 所有 发布者(
Publisher
) 的操作符(func
)的实现都是通过Publishers
内部定义类型实现的。
/// A namespace for types that serve as publishers.
///
/// The various operators defined as extensions on ``Publisher`` implement
/// their functionality as classes or structures
/// that extend this enumeration. For example,
/// the `contains(_:)` operator returns a `Publishers.Contains` instance.
@available(macOS 10.15, iOS 13.0, tvOS 13.0, watchOS 6.0, *)
public enum Publishers {
}
复制代码
同样的 Subscribers
也是 用作订阅者的类型的名称空间。 AnyCancellable
是对 Cancellable
的类型摸除。
在 Subscribers
的 extension
中官方提供了 Subscribers.Sink
,Subscribers.Assign
等。 Subscribers.Sink
和 Publisher
的 func sink(xx)
对应,Subscribers.Sink
遵守 Cancellable
协议。 Subscribers.Assign
与之类似。
# 使用 AnySubscriber 定义 ViewModel.Input
ViewModel
的定义和 RxSwift补充 中所阐述的一样,这里需要将 Input
协议从 func xx
转变为订阅者 属性。使用 AnySubscriber
不限制内部具体使用类型,同时隐藏内部实现。
protocol TextureDemoViewModelInputs {
var viewDidLoadSubscriber: AnySubscriber<Void, Never> { get }
var refreshSubscriber: AnySubscriber<Void, Never> { get }
var moreDataSubcriber: AnySubscriber<Void, Never> { get }
}
复制代码
例如:viewDidLoadSubscriber
对应原来的 func viewDidload()
。 在 ViewModel
对其实现有两种方式:
-
直接使用
Subject
转换:fileprivate let moreDataSubject = PassthroughSubject<Void, Never>() var moreDataSubcriber: AnySubscriber<Void, Never> { self.moreDataSubject.asAnySubscriber() } 复制代码
-
使用使用
Subscribers.xxx
创建:var viewDidLoadSubscriber: AnySubscriber<Void, Never> { let sinkSubscriber = Subscribers.Sink<Void, Never>.init { _ in print("viewDidLoad Sink finished! ____&") } receiveValue: { [weak self] _ in self?.queryNewData() } return .init(sinkSubscriber) } 复制代码
# 为 MJRefresh 添加 Combine 支持
通过对 Input
的改造,我们很容易通过 Subscribers.Sink
实现 MJRefreshHeader
和 MJRefreshFooter
结束刷新的 subscriber
:
extension MJRefreshHeader {
// @NOTE: - 可以是 方法, 也可以是 计算属性, 都不支持多次加入到 发布者 中
func subscriber() -> AnySubscriber<Void, Never> {
let sinkSubscriber = Subscribers.Sink<Void, Never>.init { _ in
// To be continue
} receiveValue: { [weak self] _ in
self?.endRefreshing()
}
return .init(sinkSubscriber)
}
}
extension MJRefreshFooter {
func subscriber() -> AnySubscriber<Bool, Never> {
let sinkSubscriber = Subscribers.Sink<Bool, Never>.init { _ in
// To be continue
} receiveValue: { [weak self] hasMore in
(hasMore ? { self?.endRefreshing() } : { self?.endRefreshingWithNoMoreData() })()
}
return .init(sinkSubscriber)
}
}
复制代码
对上/下拉刷新提供 Publisher
封装,需要用到自定义 Publisher
和 Subscription
来组合实现。
本文想要将发送信息的 MJRefreshComponent
返回(也返回 Void
),Subscription
定义如下:
fileprivate final class MJRefreshingSubscription<S: Subscriber, Control: MJRefreshComponent>: Subscription where S.Input == Control {
private var subscriber: S
private let control: Control
init(subscriber: S, control: Control) {
self.subscriber = subscriber
self.control = control
//control.setRefreshingTarget(self, refreshingAction: #selector(refreshing))
// FIXDE: - 注意循环引用: control -> refreshingBlock -> control
control.refreshingBlock = { [weak control] in
if let ctr = control {
_ = subscriber.receive(ctr)
}
}
}
// To be continue
func request(_ demand: Subscribers.Demand) {
guard demand > 0 else { return subscriber.receive(completion: .finished) }
// 不作任何处理, 已经在 refreshingBlock 中通知 subscriber 接收 control
}
func cancel() {
// To be continue
subscriber.receive(completion: .finished)
}
}
复制代码
Publisher
定义如下:
fileprivate final class MJRefreshingPublisher<Control: MJRefreshComponent>: Publisher {
typealias Output = Control
typealias Failure = Never
let control: Control
init(control: Control) {
self.control = control
}
// To be continue
func receive<S>(subscriber: S) where S : Subscriber, Never == S.Failure, Control == S.Input {
let subscription = MJRefreshingSubscription(subscriber: subscriber, control: control)
subscriber.receive(subscription: subscription)
}
}
复制代码
为 MJRefreshComponent
的刷新提供 Publisher
支持:
extension MJRefreshComponent {
var publisherRefreshing: AnyPublisher<MJRefreshComponent, Never> {
return MJRefreshingPublisher(control: self).eraseToAnyPublisher()
}
}
复制代码
# 通过操作符创建新的 Publisher
Outputs
协议定义如下:
protocol TextureDemoViewModelOutputs {
var newDataPublisher: AnyPublisher<[DynamicDisplayType], Never> { get }
var endRefreshPublisher: AnyPublisher<Void, Never> { get }
var moreDataPublisher: AnyPublisher<[DynamicDisplayType], Never> { get }
var endMoreRefreshPublisher: AnyPublisher<Bool, Never> { get }
var toastPublisher: AnyPublisher<String, Never> { get }
}
复制代码
在 ViewModel
中我们任然需要通过 PassthroughSubject
(或者 CurrentValueSubject
) 来控制数据请求的时机(send(value)
, 或者 asAnySubscriber()
)。使用的 Subject
定义如下:
fileprivate let refreshSubject = PassthroughSubject<Void, Never>()
fileprivate let topicSubject = PassthroughSubject<Void, Never>()
fileprivate let moreDataSubject = PassthroughSubject<Void, Never>()
复制代码
Outputs
中 Publisher
都是通过他们转换而来的。注意:需要使用 share()
操作符,来达到 Publisher
共享的作用(简单理解为引用类型和值类型的区别),下面是需要共享的 publisher
:
// FIXED: - 必须使用存储属性, share() 才能保证多次订阅不会产生多次的请求
private lazy var newDataResultPublisher: AnyPublisher<Result<[DynamicDisplayType], BundleJsonDataError>, Never> = {
self.createNewDataPublisher()
.share()
.eraseToAnyPublisher()
}()
private lazy var moreDataResultPublisher: AnyPublisher<[DynamicDisplayType]?, Never> = {
self.createMoreDataPublisher()
.share()
.eraseToAnyPublisher()
}()
复制代码
对 Outputs
中其他发布者转换入下:
var newDataPublisher: AnyPublisher<[DynamicDisplayType], Never> {
return self.newDataResultPublisher
.compactMap { result -> [DynamicDisplayType]? in
if case .success(let list) = result {
return list
}
return nil
}
.onMainScheduler()
}
var endRefreshPublisher: AnyPublisher<Void, Never> {
self.newDataResultPublisher
.map { _ in }
.onMainScheduler()
}
var moreDataPublisher: AnyPublisher<[DynamicDisplayType], Never> {
self.moreDataResultPublisher
.compactMap { $0 }
.onMainScheduler()
}
var endMoreRefreshPublisher: AnyPublisher<Bool, Never> {
self.moreDataResultPublisher
.map { _ in kDynamicFileIndex < 5 }
.merge(with: self.newDataResultPublisher.map { _ in true })
.onMainScheduler()
}
var toastPublisher: AnyPublisher<String, Never> {
self.newDataResultPublisher
.compactMap { result -> String? in
switch result {
case .success(_):
return nil
case .failure(_):
return ">_< 数据丢失了!"
}
}
.merge(with: self.moreDataResultPublisher.compactMap { $0 == nil ? ">_< 数据丢失了!" : nil })
.onMainScheduler()
}
复制代码
# 在 VC 中使用
至此,ViewModel
和 MJRefresh
封装完成。在 VC
中替换 MJRefresh
的 block
为 publisher
func eventListen() {
mjHeader.publisherRefreshing
.map { _ in }
.receive(subscriber: viewModel.input.refreshSubscriber)
mjFooter.publisherRefreshing
.map { _ in }
.receive(subscriber: viewModel.input.moreDataSubcriber)
}
复制代码
绑定 viewModel
func bindViewModel() {
// To be contniue 为什么没有使用 subscriber?
viewModel.output.newDataPublisher
.sink { [weak self] list in
self?.reloadData(with: list)
}
.store(in: &cancellable)
viewModel.output.endRefreshPublisher
.receive(subscriber: mjHeader.subscriber())
viewModel.output.moreDataPublisher
.sink { [weak self] list in
self?.insertData(with: list)
}
.store(in: &cancellable)
viewModel.output.endMoreRefreshPublisher
.receive(subscriber: mjFooter.subscriber())
viewModel.output.toastPublisher
.sink { [weak self] msg in
self?.toast.showCenter(message: msg)
}
.store(in: &cancellable)
}
复制代码
# 补充
文中方法补充:
-
queryNewData
func queryNewData() { kDynamicFileIndex = 0 self.refreshSubject.send() self.topicSubject.send() } 复制代码
-
asAnySubscriber
extension Subject { public func asAnySubscriber() -> AnySubscriber<Self.Output, Self.Failure> { .init(self) } } 复制代码
-
onMainThread
extension Publisher { public func onMainScheduler() -> AnyPublisher<Self.Output, Self.Failure> { receive(on: RunLoop.main).eraseToAnyPublisher() } } 复制代码
UIButton 的拓展
extension UIButton {
public func subscriber(forTitle state: UIControl.State) -> AnySubscriber<String, Never> {
let sinkSubscriber = Subscribers.Sink<String, Never> { _ in
} receiveValue: { [weak self] value in
self?.setTitle(value, for: state)
}
return .init(sinkSubscriber)
}
}
extension UIControl {
public func publisher(forAction event: UIControl.Event) -> AnyPublisher<UIControl, Never> {
ControlPublisher.init(control: self, event: event).eraseToAnyPublisher()
}
}
复制代码
接下来:
在下一篇博文中我们来研究下 combine
的内存管理,并补充文中 // To be contniue
的部分。
感谢您的阅读,有问题可留言。