1. 简介
对于MJRefresh
的使用,大家都已不再陌生,它提供给我们的回调方式一种是Target-Action
,另外一种是结合Block
,使用的时候例如下面这样:
tableView.mj_header = MJRefreshNormalHeader(refreshingBlock: { ... }) tableView.mj_header = MJRefreshNormalHeader(refreshingTarget: self, refreshingAction: #selector(...))
mj_header
的类型为MJRefreshHeader
,mj_footer
的类型为MJRefreshFooter
,两者均继承自MJRefreshComponent
,通过源码可以看到,两者的正在刷新回调为以下两种方式:
/** 正在刷新的回调 */ @property (copy, nonatomic, nullable) MJRefreshComponentAction refreshingBlock; /** 设置回调对象和回调方法 */ - (void)setRefreshingTarget:(id)target refreshingAction:(SEL)action;
那么我们怎么把这两种方式更加“RxSwift
”化一些?比如我们怎么这样调用tableView.rx.refreshing
?下面我们分别看一下上面两种方式如何“RxSwift
”化;
2. button.rx.tap的实现
我们想让通过Target-Action
这种方式来响应事件,这个时候我们可以结合UIButton
的点击事件来看“RxSwift
”化是怎么做的?我们监听UIButton
的点击可以这样做:
btn.rx.tap.subscribe { (_) in }
再来看一下tap
属性,为ControlEvent
类型,并且为Reactive
给UIButton
添加的扩展属性;
extension Reactive where Base: UIButton { /// Reactive wrapper for `TouchUpInside` control event. public var tap: ControlEvent<Void> { controlEvent(.touchUpInside) } }
内部调用了函数,并返回了一个ControlEvent
,ControlEvent
专门用于描述UI
控件所产生的事件;
public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()> { ... }
在controlEvent
函数内部创建了Observable
,并且它的生命周期跟随UIControl
一起被释放,返回值为ControlEvent
类型,ControlEvent
内部保存了Observable
,并且subscribe(on: ConcurrentMainScheduler.instance)
保证了构建函数只能在主线程进行,而ControlEvent
内部提供的也有可被订阅的函数subscribe
,故外界可以监听到Observable
内部发出的信号;
public func controlEvent(_ controlEvents: UIControl.Event) -> ControlEvent<()> { let source: Observable<Void> = Observable.create { [weak control = self.base] observer in MainScheduler.ensureRunningOnMainThread() guard let control = control else { observer.on(.completed) return Disposables.create() } let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { _ in observer.on(.next(())) } return Disposables.create(with: controlTarget.dispose) } .take(until: deallocated) return ControlEvent(events: source) }
再来看ControlTarget
, 继承自RxTarget
,RxTarget
继承自NSObject
并遵守协议Disposable
,目的是为了实现dispose
方法,RxTarget
的作用主要是为了延迟释放,它的生命周期跟随当调用dispose
方法后,如果外界没有强引用RxTarget
,则会被释放掉;在ControlTarget
内部,保存了Control
和Event
,并把事件通过Callback
回调给外界,相当于把target-action
的回调方法使用闭包在构造函数内部封装了一下,可以通过闭包去直接监听,并且他的生命周期为调用dispose
函数后;
class RxTarget : NSObject , Disposable { private var retainSelf: RxTarget? override init() { super.init() self.retainSelf = self } func dispose() { self.retainSelf = nil } }
final class ControlTarget: RxTarget { typealias Callback = (Control) -> Void let selector: Selector = #selector(ControlTarget.eventHandler(_:)) weak var control: Control? let controlEvents: UIControl.Event var callback: Callback? init(control: Control, controlEvents: UIControl.Event, callback: @escaping Callback) { MainScheduler.ensureRunningOnMainThread() self.control = control self.controlEvents = controlEvents self.callback = callback super.init() control.addTarget(self, action: selector, for: controlEvents) let method = self.method(for: selector) ... } @objc func eventHandler(_ sender: Control!) { if let callback = self.callback, let control = self.control { callback(control) } } override func dispose() { super.dispose() self.control?.removeTarget(self, action: self.selector, for: self.controlEvents) self.callback = nil } }
这个时候再看看controlEvent
内部,就可以很清楚的知道当按钮点击的时候,observer
会发出一个next
事件,从而外界通过controlEvent
可以订阅到这个事件,至此整个流程结束;
let controlTarget = ControlTarget(control: control, controlEvents: controlEvents) { _ in observer.on(.next(())) }
3. 扩展MJRefresh的Target-Action的刷新机制实现
那么MJRefresh
的Target-Action
方式怎么可以实现上述这种方式呢?我们就比照着上面来就好了,首先我们想通过tableView.rx.xx
的形式访问,必须给Rective
做扩展,那么我们就扩展Rective
并且为我们上述分析的MJRefreshComponent
来做扩展,上述的RxTarget
是不能被其他模块访问的,并且ControlTarget
使用final
修饰,不能被外界所继承,相当于我们就不能直接使用它了,那么我们就自己来搞一下上述的这两个类的实现:
typealias RefreshEvent = () -> Void //避免因局部创建的时候,使用之后就释放,这里要手动控制他的释放时机 class LTTarget: NSObject, Disposable { private var retainSelf: LTTarget? override init() { super.init() self.retainSelf = self } func dispose() { self.retainSelf = nil } deinit { Logger("LTTarget deinit") } } //继承LTTarget,避免因局部创建的时候,使用之后就释放,这里要手动控制他的释放时机 final class RefreshTarget<Element: MJRefreshComponent>: LTTarget { private var element: Element //MJRefreshComponent private var event: RefreshEvent //刷新事件 init(_ element: Element, event: @escaping RefreshEvent) { self.element = element self.event = event super.init() //监听事件 element.setRefreshingTarget(self, refreshingAction: #selector(startRefresh)) } @objc private func startRefresh() { self.event() //回调给外界 } deinit { Logger("RefreshTarget deinit") } } extension Reactive where Base: MJRefreshComponent { var glt_refreshing: ControlEvent<Void> { let observable = Observable<Void>.create {[weak comp = self.base] (observer) -> Disposable in //保证在主线程执行 否则抛出异常 MainScheduler.ensureExecutingOnScheduler() guard let _comp = comp else { observer.on(.completed) return Disposables.create() } //用于处理监听MJRefresh的正在刷新事件,RefreshTarget释放时机跟随引用着glt_refreshing的变量一起 let disposeable = RefreshTarget(_comp) { observer.onNext(()) } return disposeable }.take(until: deallocated) return ControlEvent(events: observable) } }
那么我们以后就可以这样直接使用了:
tableView.mj_header?.rx.glt_refreshing.subscribe(onNext: { (_) in }) tableView.mj_footer?.rx.glt_refreshing.subscribe(onNext: { (_) in }
4. 扩展MJRefresh的Block的刷新机制实现
因为直接可以使用block
调用,所以不用再抽象一个类和Target
去做了;
extension Reactive where Base: MJRefreshComponent { var glt_block_refreshing: ControlEvent<Void> { let observable = Observable<Void>.create {[weak comp = self.base] (observer) -> Disposable in //保证在主线程执行 否则抛出异常 MainScheduler.ensureExecutingOnScheduler() guard let _comp = comp else { observer.on(.completed) return Disposables.create() } //用于处理监听MJRefresh的正在刷新事件,RefreshTarget释放时机跟随引用着glt_refreshing的变量一起 _comp.refreshingBlock = { observer.onNext(()) } return Disposables.create() }.take(until: deallocated) return ControlEvent(events: observable) } }
经过测试MJRefreshComponent
以及RefreshTarget
和LTTarget
都是可以正常释放的,具体的使用方式会在下一篇中介绍;
5. MJRefresh的header和footer的结束刷新
直接利用Reactive
给UIScrollView
做扩展,并且告诉结束的两种状态,endRefreshing
代表结束刷新,noMoreData
代表结束刷新并且没有更多数据了;在实际开发中,我们一般会无论收到任何一个的结束刷新操作,我们会同时调用header
和footer
的结束刷新状态,这样可以避免掉一些意外的Bug
,并且如果是footer
的话,会区分还有没有更多数据,所以才这样设计;另外因为mj_header
和mj_footer
为Optional
类型,保证了即使为空的时候,程序也不会Crash
;
enum LTRefreshEndState { case endRefreshing case noMoreData } extension Reactive where Base: UIScrollView { //用于外界刷新绑定、内部判断是是否有更多数据的UI状态 var endRefreshing: Binder<LTRefreshEndState> { Binder<LTRefreshEndState>(self.base) { scrollView, state in scrollView.mj_header?.endRefreshing() if state == .noMoreData { scrollView.mj_footer?.endRefreshingWithNoMoreData() }else { scrollView.mj_footer?.endRefreshing() } } } }
关于MJRefresh+RxSwift
的实际使用会在下一篇中具体介绍;
复制搜一搜分享收藏划线