Swift

RXSwift+MVVM+ObjectMapper实战使用

2019-08-12  本文已影响0人  lb_

先来说准备工作。

podFile:


提示:
由于 Moya-ObjectMapper/RxSwift 本身就有以下依赖, 因此我们可以注释掉 pod 'RxSwift'
 - Subspecs:
     - Moya-ObjectMapper/Core (2.8)
     - Moya-ObjectMapper/RxSwift (2.8)
     - Moya-ObjectMapper/ReactiveSwift (2.8)

其他三方库解释

Git-ObjectMapper

import Foundation
import ObjectMapper
import RxDataSources

struct LBPublicWelfareModel : Mappable {
    var id = 0
    var title = ""
    var description = ""

    init?(map: Map) {
    }
    
    mutating func mapping(map: Map) {
        id <- map["id"]
        title <- map["title"]
        description <- map["description"]
    }
}

Git-RxDataSources

例:(基础用法,另外分组、head、foot等其他用法)

var ds : RxTableViewSectionedReloadDataSource<LBPublicWelfareSection>!

ds = RxTableViewSectionedReloadDataSource<LBPublicWelfareSection>.init(configureCell: { (dataSource, tb, indexPath, item) -> UITableViewCell in
      let cell = tb.dequeueReusableCell(withIdentifier: "LBPublicWelfareTableViewCell", for: indexPath) as! LBPublicWelfareTableViewCell
      cell.countLab.text = "限\(item.needPeople)人"
      return cell
})

Git-Then

例:

let tableView = UITableView().then {
    $0.backgroundColor = LXSize.bgViewColor()
    $0.register(UINib.init(nibName: "LBPublicWelfareTableViewCell", bundle: nil), forCellReuseIdentifier: "LBPublicWelfareTableViewCell")
    $0.separatorStyle = .none
    $0.rowHeight = 168
}

基类、Protocol 准备

针对列表页, 我创建了一个 protocol - LBRefreshable ,针对这个协议做了针对 UIViewControllerUIScrollView 的扩展 ,就是说遵循了这个协议, 其 UIScrollView 就可以获得上拉和下拉的方法。
该类中几个重要方法:

import RxSwift
import RxCocoa
import NSObject_Rx

 //刷新状态
enum LBRefreshStatus { 
    case none
    case beingHeaderRefresh
    case endHeaderRefresh
    case beingFooterRefresh
    case endFooterRefresh
    case noMoreData
}
 //协议及扩展
protocol LBRefreshable {
    
}

extension LBRefreshable where Self : UIViewController {
    func initRefreshHeader(_ scrollView: UIScrollView, _ action: @escaping () -> Void) -> MJRefreshHeader {
        scrollView.mj_header = MJRefreshNormalHeader(refreshingBlock: { action() })
        return scrollView.mj_header
    }
    
    func initRefreshFooter(_ scrollView: UIScrollView, _ action: @escaping () -> Void) -> MJRefreshFooter {
        let foot = MJRefreshAutoNormalFooter(refreshingBlock: { action() })
        scrollView.mj_footer = foot
        return scrollView.mj_footer
    }
}

extension LBRefreshable where Self : UIScrollView {
    func initRefreshHeader(_ action: @escaping () -> Void) -> MJRefreshHeader {
        mj_header = MJRefreshNormalHeader(refreshingBlock: { action() })
        return mj_header
    }
    
    func initRefreshFooter(_ action: @escaping () -> Void) -> MJRefreshFooter {
        let foot = MJRefreshAutoNormalFooter(refreshingBlock: { action() })
        mj_footer = foot
        return mj_footer
    }
}

重点:OutputRefreshProtocol 协议 提供一个 refreshStatus 的序列,继承本协议的类通过扩展方法 autoSetRefreshHeaderStatus 设置头和尾时, 会针对这个序列进行订阅。

也就是说外界设置头和尾, 再设置 Variable.value 时, 本协议作出响应,自动处理头和尾的状态。

protocol OutputRefreshProtocol {
    var refreshStatus : Variable<LBRefreshStatus> { get }
}
extension OutputRefreshProtocol {
    func autoSetRefreshHeaderStatus(header: MJRefreshHeader?, footer: MJRefreshFooter?) -> Disposable {
        return refreshStatus.asObservable().subscribe(onNext: { (status) in
            switch status {
            case .beingHeaderRefresh:
                header?.beginRefreshing()
            case .endHeaderRefresh:
                header?.endRefreshing()
            case .beingFooterRefresh:
                footer?.beginRefreshing()
            case .endFooterRefresh:
                footer?.endRefreshing()
            case .noMoreData:
                footer?.endRefreshingWithNoMoreData()
            default:
                break
            }
        })
    }
}

具体类文件目录:

ViewModel :

(这里代码篇幅稍微有点长,但是看了看没什么能省略的,谅解)

VM 主要负责接收 获取网络数据、解析、保存数据、发送刷新状态响应。

import Foundation
import RxSwift
import RxCocoa
import NSObject_Rx

class LBPublicWelfareViewModel : NSObject {
    // 存放着解析完成的模型数组
    let vModels = Variable<[LBPublicWelfareSection]>([])
    // 记录当前的索引值
    var index: Int = 0
}

extension LBPublicWelfareViewModel : LBViewModelType{
    typealias Input = LBInput
    typealias Output = LBOutput
    struct LBInput {
        //入参
        let categoryId : String
    }
    struct LBOutput : OutputRefreshProtocol {
        // collection的sections数据
        let sections:Driver<[LBPublicWelfareSection]>
        // 外界通过该属性告诉viewModel加载数据(传入的值是为了标志是否重新加载)
        let requestCommond = PublishSubject<Bool>()
        // 告诉外界的collection当前的刷新状态
        let refreshStatus = Variable<LBRefreshStatus>(.none)
        init(sections:Driver<[LBPublicWelfareSection]>) {
            self.sections = sections
        }
    }
    
    func transform(input: LBPublicWelfareViewModel.LBInput) -> LBPublicWelfareViewModel.LBOutput {
        let temp_Sections = vModels.asObservable().map { (sections) -> [LBPublicWelfareSection] in
            return sections.map({ (sec) -> LBPublicWelfareSection in
                return LBPublicWelfareSection(items: sec.items)
            })
            }.asDriver(onErrorJustReturn: [])
        
        let outPut = LBOutput(sections: temp_Sections)
        outPut.requestCommond.asObserver().subscribe(onNext: { (isReloadData) in
            self.index = isReloadData ? 0 : (self.index + 1)
            var dataAry : [LBPublicWelfareModel] = []
            if self.vModels.value.count > 0{
                dataAry = self.vModels.value[0].items
            }
            var currentDataAry = NSArray(array: dataAry)
            let parameter : [String:Any] = self.index == 0 ? ["category":input.categoryId,"beginNum":0,"pageLineMax":10] : ["category":input.categoryId,"beginNum":dataAry.count,"pageLineMax":10]
            
            LBRequestManager.postNetWorkRequest(withURL: "sheYuan/getActivity", parameters: parameter, response: { (item, err) in
                print(item)
                outPut.refreshStatus.value = .endHeaderRefresh
                outPut.refreshStatus.value = .endFooterRefresh
                if item != nil{
                    //字典转模型
                    let ary : [ [String:Any] ] = (item!.data as? [[String:Any]] ?? [])
                    let newNotifyModelAry = ary.map({ (dic) -> LBPublicWelfareModel in
                        return LBPublicWelfareModel(JSON: dic) ?? (LBPublicWelfareModel(JSON: [:]))!
                    })
                    let mut = NSMutableArray(array: currentDataAry)
                    mut.addObjects(from: newNotifyModelAry)
                    self.index == 0 ? currentDataAry = newNotifyModelAry as NSArray : (currentDataAry = (mut as NSArray))
                    self.vModels.value = [LBPublicWelfareSection(items: currentDataAry as! [LBPublicWelfareModel])]
                    ary.count < 10 ? (outPut.refreshStatus.value = .noMoreData) : (outPut.refreshStatus.value = .endFooterRefresh)
                }
            })
        }).disposed(by: rx.disposeBag)
        return outPut
    }
}

Model :

这里要注意 LBPublicWelfareSectionitem 写法 因为这里就是在VC中绑定 dataSource 时获取的 Item 格式。

import Foundation
import ObjectMapper
import RxDataSources

struct LBPublicWelfareSection {
    var items : [LBPublicWelfareModel]
}

extension LBPublicWelfareSection : SectionModelType{
    init(original: LBPublicWelfareSection, items: [LBPublicWelfareModel]) {
        self = original
        self.items = items
    }
    typealias item = LBPublicWelfareModel
}

struct LBPublicWelfareModel : Mappable {
    var id = 0
    var title = ""
    init?(map: Map) {
    }
    
    mutating func mapping(map: Map) {
        id <- map["id"]
        title <- map["title"]
    }
}

View

目前所写这个页面只有一个 cell ,常规写法,这里就不赘述了。 View 里一般会做什么?
这里举个其他页面的例子,View 有一个搜索框,封装到这个目录下面的一个类中,然后在 VC中对改输入框的输入流序列进行订阅,响应,然后直接 self.vmOutput.requestCommond.onNext(true) 发送事件,让 VM 去重新拉取数据就可以了。

ViewController

VC 中的写法就比较简单了,因为我们目前把 tableview 放到了 VC 里,追求极致 MVVM 的也可以封装到一个 View 中, 然后交由 VM 来管理数据和 tableView 的页面处理。

具体就是实例化一个 VM, 一个 tableViewvm 用来管理数据源,和 tableView 的刷新状态管理。

import Then
import SnapKit
import RxCocoa
import RxSwift
import RxDataSources

class LBPublicWelfareViewController: LBBaseViewController {
    
    let vm = LBPublicWelfareViewModel()   //ViewModel
    
    var ds : RxTableViewSectionedReloadDataSource<LBPublicWelfareSection>!   //RxDataSources管理者
    
    var vmOutput : LBPublicWelfareViewModel.Output!
    
    let tableView = UITableView().then {
        $0.backgroundColor = LXSize.bgViewColor()
        $0.register(UINib.init(nibName: "LBPublicWelfareTableViewCell", bundle: nil), forCellReuseIdentifier: "LBPublicWelfareTableViewCell")
        $0.separatorStyle = .none
        $0.rowHeight = 168
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setUpUI()
        bindView()
    }
    
}
fileprivate func setUpUI(){
    self.view.backgroundColor = LXSize.bgViewColor()
    self.title = "公益活动"
    view.addSubview(tableView)
    tableView.snp.makeConstraints { (maker) in
            maker.top.equalToSuperview().offset(5)
            maker.left.right.equalToSuperview()
            maker.bottom.equalToSuperview()
        }
}

重点

extension LBPublicWelfareViewController : LBRefreshable , UITableViewDelegate{
    fileprivate func bindView(){
        ds = RxTableViewSectionedReloadDataSource<LBPublicWelfareSection>.init(configureCell: { (dataSource, tb, indexPath, item) -> UITableViewCell in
            let cell = tb.dequeueReusableCell(withIdentifier: "LBPublicWelfareTableViewCell", for: indexPath) as! LBPublicWelfareTableViewCell
            cell.titleLab.text = item.title
            cell.descriptionLab.text = item.description
            /*...*/
            return cell
        })

        tableView.rx.itemSelected.subscribe { [weak self] (indexPath) in
            let section = self?.ds.sectionModels[0].items[indexPath.element?.row ?? 0]
            let vc = LBWebDetailViewController()
            vc.callBlockFunc {
                self?.vmOutput.requestCommond.onNext(true)
            }
            self?.navigationController?.pushViewController(vc, animated: true)
        }.disposed(by: disposeBag)

        tableView.rx.setDelegate(self).disposed(by: disposeBag)

        let vmInput = LBPublicWelfareViewModel.Input(categoryId: "gongYi")
        vmOutput = vm.transform(input: vmInput)
        
        vmOutput.sections.asDriver().drive(tableView.rx.items(dataSource: ds)).disposed(by: disposeBag)
        
        let head = initRefreshGifHeader(tableView) {
            self.vmOutput.requestCommond.onNext(true)
        }
        
        let foot = initRefreshFooter(tableView) {
            self.vmOutput.requestCommond.onNext(false)
        }
        
        vmOutput.autoSetRefreshHeaderStatus(header: head, footer: foot).disposed(by: disposeBag)
        
        tableView.mj_header.beginRefreshing()
    }
}

VC 中核心就是 VMvmOutput 每调用 .requestCommond.onNext(false) 时,就会来到 VM 中,因为 VM 订阅了 requestCommondVM 请求并解析完毕数据后 ,改变 vmOutputvaluevModels
value, 刷新基类协议中接收到 value 改变响应,通知对应的 scrollview 改变刷新状态,并且 vc 响应 RxTableViewSectionedReloadDataSource 的事件。

至此,一个简单的 RxSwift + MVVM的实例已经完成。对应复杂的页面,其实核心逻辑也是如此,只是封装到应该对应的类中。 通过序列的订阅,实现通讯。减少依赖耦合。

上一篇下一篇

猜你喜欢

热点阅读