先來說準備工作。
podFile:

提示:
由于
Moya-ObjectMapper/RxSwift 本身就有以下依賴, 因此我們可以注釋掉 pod 'RxSwift'
- Subspecs:
- Moya-ObjectMapper/Core (2.8)
- Moya-ObjectMapper/RxSwift (2.8)
- Moya-ObjectMapper/ReactiveSwift (2.8)
其他三方庫解釋
-
ObjectMapper: 用于字典 /json轉(zhuǎn)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組合使用,寫法下文會交代。
例:(基礎(chǔ)用法,另外分組、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 準備
針對列表頁, 我創(chuàng)建了一個 protocol - LBRefreshable ,針對這個協(xié)議做了針對 UIViewController 、 UIScrollView 的擴展 ,就是說遵循了這個協(xié)議, 其 UIScrollView 就可以獲得上拉和下拉的方法。
該類中幾個重要方法:
import RxSwift
import RxCocoa
import NSObject_Rx
//刷新狀態(tài)
enum LBRefreshStatus {
case none
case beingHeaderRefresh
case endHeaderRefresh
case beingFooterRefresh
case endFooterRefresh
case noMoreData
}
//協(xié)議及擴展
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 協(xié)議 提供一個 refreshStatus 的序列,繼承本協(xié)議的類通過擴展方法 autoSetRefreshHeaderStatus 設(shè)置頭和尾時, 會針對這個序列進行訂閱。
也就是說外界設(shè)置頭和尾, 再設(shè)置 Variable 的 .value 時, 本協(xié)議作出響應(yīng),自動處理頭和尾的狀態(tài)。
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 主要負責(zé)接收 獲取網(wǎng)絡(luò)數(shù)據(jù)、解析、保存數(shù)據(jù)、發(fā)送刷新狀態(tài)響應(yīng)。
import Foundation
import RxSwift
import RxCocoa
import NSObject_Rx
class LBPublicWelfareViewModel : NSObject {
// 存放著解析完成的模型數(shù)組
let vModels = Variable<[LBPublicWelfareSection]>([])
// 記錄當前的索引值
var index: Int = 0
}
extension LBPublicWelfareViewModel : LBViewModelType{
typealias Input = LBInput
typealias Output = LBOutput
struct LBInput {
//入?yún)? let categoryId : String
}
struct LBOutput : OutputRefreshProtocol {
// collection的sections數(shù)據(jù)
let sections:Driver<[LBPublicWelfareSection]>
// 外界通過該屬性告訴viewModel加載數(shù)據(jù)(傳入的值是為了標志是否重新加載)
let requestCommond = PublishSubject<Bool>()
// 告訴外界的collection當前的刷新狀態(tài)
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{
//字典轉(zhuǎn)模型
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 ,常規(guī)寫法,這里就不贅述了。 View 里一般會做什么?
這里舉個其他頁面的例子,View 有一個搜索框,封裝到這個目錄下面的一個類中,然后在 VC中對改輸入框的輸入流序列進行訂閱,響應(yīng),然后直接 self.vmOutput.requestCommond.onNext(true) 發(fā)送事件,讓 VM 去重新拉取數(shù)據(jù)就可以了。
ViewController
VC 中的寫法就比較簡單了,因為我們目前把 tableview 放到了 VC 里,追求極致 MVVM 的也可以封裝到一個 View 中, 然后交由 VM 來管理數(shù)據(jù)和 tableView 的頁面處理。
具體就是實例化一個
VM, 一個tableView,vm用來管理數(shù)據(jù)源,和tableView的刷新狀態(tài)管理。
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每調(diào)用.requestCommond.onNext(false)時,就會來到VM中,因為VM訂閱了requestCommond,VM請求并解析完畢數(shù)據(jù)后 ,改變vmOutput的value和vModels
的value, 刷新基類協(xié)議中接收到value改變響應(yīng),通知對應(yīng)的scrollview改變刷新狀態(tài),并且vc響應(yīng)RxTableViewSectionedReloadDataSource的事件。
至此,一個簡單的 RxSwift + MVVM的實例已經(jīng)完成。對應(yīng)復(fù)雜的頁面,其實核心邏輯也是如此,只是封裝到應(yīng)該對應(yīng)的類中。 通過序列的訂閱,實現(xiàn)通訊。減少依賴耦合。