先来说准备工作。
podFile:

提示:
由于
Moya-ObjectMapper/RxSwift
本身就有以下依赖, 因此我们可以注释掉 pod 'RxSwift'
- Subspecs:
- Moya-ObjectMapper/Core (2.8)
- Moya-ObjectMapper/RxSwift (2.8)
- Moya-ObjectMapper/ReactiveSwift (2.8)
其他三方库解释
-
ObjectMapper
: 用于字典 /json
转model
。写法参考下面model的写法。
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"]
}
}
-
RxDataSources
: 主要用于 Rx和tableview
组合使用,写法下文会交代。
例:(基础用法,另外分组、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
})
-
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
}
- NSObject+Rx :
参考 : Git-NSObject-Rx
基类、Protocol 准备
针对列表页, 我创建了一个 protocol
- LBRefreshable
,针对这个协议做了针对 UIViewController
、 UIScrollView
的扩展 ,就是说遵循了这个协议, 其 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 :
这里要注意 LBPublicWelfareSection
的 item
写法 因为这里就是在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
, 一个tableView
,vm
用来管理数据源,和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()
}
}
- setUpUI方法,简单直接过
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 中核心就是
VM
的vmOutput
每调用.requestCommond.onNext(false)
时,就会来到VM
中,因为VM
订阅了requestCommond
,VM
请求并解析完毕数据后 ,改变vmOutput
的value
和vModels
的value
, 刷新基类协议中接收到value
改变响应,通知对应的scrollview
改变刷新状态,并且vc
响应RxTableViewSectionedReloadDataSource
的事件。
至此,一个简单的 RxSwift + MVVM的实例已经完成。对应复杂的页面,其实核心逻辑也是如此,只是封装到应该对应的类中。 通过序列的订阅,实现通讯。减少依赖耦合。
网友评论