您好,欢迎来到华拓科技网。
搜索
您的当前位置:首页RxSwift + MJRefresh 自动管理刷新状态

RxSwift + MJRefresh 自动管理刷新状态

来源:华拓科技网

枚举

  首先定义一个有关刷新状态的枚举类型:

/// 可按照自己的需求添加,由于我没有用到 mj_footer.beginRefreshing(),
/// 所以没有定义相关的枚举。
enum RefreshStatus {
    case none
    case beingHeaderRefresh
    case endHeaderRefresh
    case endFooterRefresh
    // 这个枚举由于在我项目中经常用到,所以我定一个关联值的枚举。
    // 项目中需要:
    //  - 数据为空的时候隐藏 `mj_footer`,否则显示;
    //  - 然后判断没有更多数据就调用 `endRefreshingWithNoMoreData()`
    //    否则 `endRefreshing()`
    case footerStatus(isHidden: Bool, isNoMoreData: Bool)
}
复制代码

  无需过多纠结,后面会演示枚举如何使用。

BehaviorSubject

  接下来要介绍一个跟 RxSwift 有关的一个类型 BehaviorSubject,我们会在文章用到它。

  BehaviorSubject 向所有订阅者发布事件,并向新的订阅者提供最近(或最初)的值。

  怎么理解?来看看代码:

func addObserver(_ id: String) -> Disposable {
    return subscribe { print("Subscription:", id, "Event:", $0) }
}
    
let disposeBag = DisposeBag()
let subject = BehaviorSubject(value: "?")

subject.addObserver("1").disposed(by: disposeBag)
subject.onNext("?")
subject.onNext("?")

subject.addObserver("2").disposed(by: disposeBag)
subject.onNext("?️")
subject.onNext("?️")

/**
Subscription: 1 Event: next(?)
Subscription: 1 Event: next(?)
Subscription: 1 Event: next(?)
Subscription: 2 Event: next(?)
Subscription: 1 Event: next(?️)
Subscription: 2 Event: next(?️)
Subscription: 1 Event: next(?️)
Subscription: 2 Event: next(?️)
Subscription: 1 Event: next(?)
Subscription: 2 Event: next(?)
Subscription: 1 Event: next(?)
Subscription: 2 Event: next(?)
*/
复制代码

  总结一下:BehaviorSubject 从上至下接收它发出的最新(原始值也属于发出的事件元素)值。并向新的订阅者提供最新值,所以这里我们 订阅2号 会接收到前面发出的最新元素,稍后才是 订阅2号 自己发出的元素事件。

  这里请允许我搬布官方的图例来说明一下:

  如你所见,紫灯的时候订阅,会接收到紫灯以及之后的所有元素。绿灯(可以理解为我们的 订阅2号,也就是传说中的新订阅者)的时候订阅,接收到绿灯以及之后的所有元素。

  图片来源:,对 RxSwift 感兴趣的同学也可以看下我最近发布的 RxSwift 系列文章。

协议

  接下来我们要用到协议,用来封装和刷新状态有关的东西。对 Swift 协议还不是太明白的可以继续看上面那位大佬写的文章:

开始

  好了,下面我们就用上面学到的所有知识来写一个自动管理刷新状态案例。

  假设有这样一个需求:

// ViewController.swift

// 两个闭包的参数默认为 nil,根据参数自动创建 mj_header 或 mj_footer,
// 不传参数则不创建。自动管理刷新状态。
viewModel.refreshStatusBind(to: tableView, {
    // 处理头部刷新。
}) {
    // 处理尾部刷新。
}.disposed(by: bag)
复制代码

  如何做到这一点?看到方法是从 viewModel 里面调出来的,那我们就去 viewModel 里面看一看究竟。

class ViewModel: Refreshable {

    lazy var list = Variable<[MnlDakaCommentModel]>([])
    let refreshStatus = BehaviorSubject(value: RefreshStatus.none)
    let reload = PublishSubject<Bool>()
    let bag = DisposeBag()
    
    init() {
        reload.subscribe(onNext: { [weak self] (isDown) in
            guard let `self` = self else {
                return
            }
            // 发送请求
            MnlAssetLoader.load(.dakaComment(params: self.params)) { (result) in 
                let list = result.value?["list"].arrayObject
                let models = decode([MnlDakaCommentModel].self, from: list) ?? []
            }
        }
    }
}
复制代码

  好了,为了简洁删除了部分代码,但该有的还是有,而且 ViewModel 里面我确实没有创建 refreshStatusBind(to:) 方法。

  这就奇怪了,究竟方法从何而来?答案在于协议。注意我们开始签了一个 Refreshable 的协议,refreshStatusBind(to:) 是在里面定义的。那我们就去看看,这个方法究竟是什么?为什么传几个参数进去就能自动创建刷新控件并管理其状态了呢?

Refreshable

  首先,我们定义了一个 Refreshable 协议:

protocol Refreshable {
    var refreshStatus: BehaviorSubject<RefreshStatus> { get }
}
复制代码

  这里你就知道了吧?任何实现 Refreshable 必须实现 refreshStatus 属性,如果你足够眼尖应该看到,上面的 ViewModel 同样定义一个类型一样 refreshStatus 属性,为的就是实现协议中规定的属性。

  好了,有个这个属性之后,我们就可以愉快的管理刷新状态了,比如想让它结束刷新,我们可以拿到 refreshStatus 属性,比如在 ViewModel 里,我们可以这样:

// 发送请求
MnlAssetLoader.load(.dakaComment(params: self.params)) { (result) in 
    let list = result.value?["list"].arrayObject
    let models = decode([MnlDakaCommentModel].self, from: list) ?? []
    // 请求完成需要结束刷新:
    // refreshStatus.onNext(.endFooterRefresh)
    // 或者判断没有更多数据时:
    // refreshStatus.onNext(isHidden: false, isNoMoreData: true)
}
复制代码

  这时你肯定问了,凭什么我这样发送消息就可以管理刷新状态了?你逗我呢?之前讲 BehaviorSubject 的时候不是有讲到订阅 (subscribe) 吗?既然这里发送消息了,肯定会在接收到发出的元素的时候做了什么处理吧?

  问得好!问得非常好!问得太————好了。好吧,我老实交代,就来说下接收到元素时我都做了什么?

extension Refreshable {
    
    func refreshStatusBind(to scrollView: UIScrollView, _ header: (() -> Void)? = nil, _ footer: (() -> Void)? = nil) -> Disposable {
        
        if header != nil {
            scrollView.mj_header = MJRefreshNormalHeader {
                // 处理头部方法时结束尾部刷新。
                scrollView.mj_footer?.endRefreshing()
                header?()
            }
        }
        if footer != nil {
            scrollView.mj_footer = MJRefreshAutoNormalFooter {
                // 处理尾部方法时结束头部刷新。
                scrollView.mj_header?.endRefreshing()
                footer?()
            }
        }
        
        return refreshStatus.subscribe(onNext: { (status) in
            switch status {
            case .none:
                // 未发生任何状态事件时隐藏尾部。
                scrollView.mj_footer?.isHidden = true
            case .beginHeaderRefresh:
                scrollView.mj_header?.beginRefreshing()
            case .endHeaderRefresh:
                scrollView.mj_header?.endRefreshing()
            case .endFooterRefresh:
                scrollView.mj_footer?.endRefreshing()
            case .endAllRefresh:
                // 结束全部拉刷新
                scrollView.mj_header?.endRefreshing()
                scrollView.mj_footer?.endRefreshing()
            case .footerStatus(let isHidden, let isNone):
                // 根据关联值确定 footer 的状态。
                scrollView.mj_footer?.isHidden = isHidden
                // 处理尾部状态时,如果之前正在刷新头部,则结束刷新,
                // 至此,我们无需写判断结束头部刷新的代码,在这里自动处理。
                scrollView.mj_header?.endRefreshing()
                if isNone {
                    scrollView.mj_footer?.endRefreshingWithNoMoreData()
                }else {
                    scrollView.mj_footer?.endRefreshing()
                }
            }
        })
    }
}
复制代码

  给 Refreshable 加一个扩展,我们先来看方法的第一部分:创建头部和尾部刷新控件。这段代码很容易看懂,结合前面放在 ViewController.swift 里的代码:

// 两个闭包的参数默认为 nil,根据参数自动创建 mj_header 或 mj_footer,
// 不传参数则不创建。自动管理刷新状态。
viewModel.refreshStatusBind(to: tableView, {
    // 处理头部刷新。
}) {
    // 处理尾部刷新。
}.disposed(by: bag)
复制代码

  无非就是传闭包参数的时候创建对应的刷新控件,并把传进去的闭包作为控件的刷新事件。因为我已经把 UIScrollView 作为参数传进来了,所以可以直接拿到它创建刷新控件。

  在到第二部分,这就是一个真正监听状态改变的部分了,是根据发出的消息做出改变的反应。这样,我们在 ViewModel 里发送消息,这里就能接收到并作出对应的改变。

Demo

  贴下完整代码。

ViewController.swift

import UIKit
import RxSwift

class ViewController: UITableViewController {

    lazy var viewModel = ViewModel()
    let bag = DisposeBag()

    override func viewDidLoad() {
        super.viewDidLoad()
        
        /// 创建刷新控件。
        viewModel.refreshStatusBind(to: tableView, { [weak self] in
            // 处理头部刷新。
            self?.viewModel.reload.onNext(false)
        }) { [weak self] in
            // 处理尾部刷新。
            self?.viewModel.reload.onNext(true)
        }.disposed(by: bag)
        
        // 给 viewModel 中的 reload 发送消息,让其请求数据。
        // 参数 Bool 表示是否上拉。
        viewModel.reload.onNext(false)
    }
}
复制代码

ViewModel.swift

import UIKit
import RxSwift

class ViewModel: Refreshable {

    lazy var list = Variable<[Model]>([])
    let refreshStatus = BehaviorSubject(value: RefreshStatus.none)
    let reload = PublishSubject<Bool>()
    let bag = DisposeBag()

    init() {
        reload.subscribe(onNext: { [weak self] (isReload) in
        
            guard let `self` = self else {
                return
            }
            
            MnlAssetLoader.load(.dakaComment(params: self.params)) { (result) in
            
                let list = result.value?["list"].arrayObject
                let models = decode([MnlDakaCommentModel].self, from: list) ?? []
                
                self.list.value = isReload ? models : self.list.value + models
                
                let count = result.value?["count"].int ?? 0
                // 发送刷新状态给订阅者,让其作出改变。
                // 如果列表个数和总数相等,则判断它为没有更多数据。
                self.refreshStatus.onNext(.footerStatus(isHidden: self.list.value.isEmpty,
                                                        isNoMoreData: self.list.value.count == count))
            }
        }).disposed(by: bag)
    }
}
复制代码

  关于 DisposeBag,其实就是一个资源回收包, 使用 Rx 代码会占用一些资源,我们把这些资源都添加到 bag 里面,这样在其对应的引用被 deinit 后,资源会被回收。

  详情可以查阅官方文档:。

转载于:https://juejin.im/post/5a4d7f1b51882512ae130db2

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- huatuo6.cn 版权所有 赣ICP备2024042791号-9

违法及侵权请联系:TEL:199 18 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务