
Note: 以上即為我們需要實(shí)現(xiàn)的效果,可以在 RxBasicInterface 拿到基本框架的代碼直接著手開(kāi)發(fā)。如果你對(duì)這個(gè)使用 RxSwift 實(shí)現(xiàn)的基本框架感興趣的話(huà),可以查看文章:Rx 項(xiàng)目基本框架的構(gòu)建 ,而當(dāng)前最終代碼: 信息流 Demo。
在還沒(méi)遇到 ReactorKit 這個(gè)框架之前,我使用 RxSwift + MVVM 去構(gòu)建如圖的信息流時(shí),確實(shí)為我?guī)?lái)很多好處:
- 層級(jí)更加清晰,分工和職能更加明確
- 大幅度解耦控制器類(lèi),代碼的邏輯體現(xiàn)更為明朗、易看
- 響應(yīng)式編程,雜散的代碼塊轉(zhuǎn)化為了集中的單向代碼流
- 可維護(hù)性和可測(cè)試性都極大的增強(qiáng)
- 不再增加一些所謂的語(yǔ)法糖,代碼也很 ''甜''
....
確實(shí)是這樣子,沒(méi)使用 RxSwift + MVVM 時(shí),我在代碼中使用了挺多語(yǔ)法糖的,舉個(gè) NotificationCenter 使用語(yǔ)法糖的例子:
// 在需使用到 Selector 類(lèi)前添加以下代碼
private extension Selector {
static let handleFunction = #selector(ExampleViewController.handleFunction)
}
// 在類(lèi)中的使用就變得很簡(jiǎn)單
NotificationCenter.default.addObserver(self, selector: .handleFunction, name: .notificationName, object: nil)
其實(shí),以上的語(yǔ)法糖,我在使用 RxSwift 之前,確實(shí)覺(jué)得它讓代碼看起來(lái)更好看了些。但在使用 RxSwift 之后,真心覺(jué)得,使用這個(gè)語(yǔ)法糖,就是在每個(gè)類(lèi)前面加了頂綠帽子,只是增長(zhǎng)了代碼的長(zhǎng)度而已...
而使用 RxSwift ,我們不再需要添加這個(gè)語(yǔ)法糖了。
但是,使用 RxSwift + MVVM ,還是一直存在幾個(gè)困惑:
- 我需要在 ViewModel、Service 類(lèi)中添加多個(gè) Observer、Observable,而有些 Observer 又得是 Observable,這樣一來(lái),我們需要額外的去注意這些屬性的應(yīng)用場(chǎng)合
- ViewModel 作為其中主要的處理器,如果其控制器類(lèi)比較復(fù)雜,那么它就需要管理很多屬性,極大的增加了我們的使用負(fù)擔(dān)
- 代碼復(fù)用率比較低
其實(shí)對(duì)于以上問(wèn)題,我曾經(jīng)想過(guò)使用面向協(xié)議編程,讓一些重復(fù)使用到的代碼,或者像一些列表類(lèi)必須實(shí)現(xiàn)的方法定義成協(xié)議,讓類(lèi)去遵循協(xié)議,實(shí)現(xiàn)協(xié)議方法,進(jìn)一步讓該類(lèi)的實(shí)現(xiàn)更加合理化,條理更加清晰。但是,不可避免的要定義很多協(xié)議,實(shí)現(xiàn)很多協(xié)議方法...
那么,有沒(méi)有一個(gè)框架,可以進(jìn)一步增強(qiáng) RxSwfit + MVVM 的優(yōu)勢(shì),削減其劣勢(shì)呢?
直到遇到了 ReactorKit 這個(gè)框架,解決了我所有的困惑。
認(rèn)識(shí) ReactorKit
考察
ReactorKit 是 Jeon Suyeol 的作品,而
Jeon Suyeol 發(fā)布了很多富有創(chuàng)造性的框架,如 Then,URLNavigator,SwiftyImage 以及一些開(kāi)源項(xiàng)目 RxTodo,Drrrible。同時(shí),他也是多個(gè)組織的成員( RxSwiftCommunity,Moya,SwiftKorea...),所以我們完全可以放心的使用這個(gè)框架,完全不需要去擔(dān)心這個(gè)框架后期維護(hù)的問(wèn)題。其實(shí)這個(gè)框架的思想并不復(fù)雜,即使 Jeon Suyeol 不再維護(hù)該框架,我們也完全可以按照他的思想,寫(xiě)個(gè)類(lèi)似的框架供自己使用。
優(yōu)點(diǎn)
- 分工、職能更進(jìn)一步清晰、明朗
- 更進(jìn)一步的模塊化和響應(yīng)式,讓代碼更便于管理
- 可由我們熟悉的 用戶(hù)行為 折射到界面熟悉的 狀態(tài)行為
使用 ReactorKit
使用介紹
ReactorKit 是一個(gè)輕量的響應(yīng)式編程框架,我們把所有的視圖(UIView)、界面(UIViewController)都當(dāng)成 View,而 View 主要是被用戶(hù)直接操作的層級(jí),我們通過(guò)監(jiān)測(cè)用戶(hù)在 View 上的行為,反饋給 Reactor(響應(yīng)器),經(jīng)由響應(yīng)器處理之后把響應(yīng)狀態(tài)傳遞給 View 層。最后, View 顯示最終的傳遞的狀態(tài);簡(jiǎn)單來(lái)說(shuō),即 View 層只發(fā)出行為,而 Reactor 只發(fā)出狀態(tài),相互把對(duì)方所需要的東西傳遞給對(duì)方,構(gòu)成一條響應(yīng)式的序列。

那么,我們先從響應(yīng)器 Reactor 著手,先分析用戶(hù)行為,再在其中將用戶(hù)行為轉(zhuǎn)換為可呈現(xiàn)在 View 上的 State。
Reactor
對(duì)于響應(yīng)器來(lái)說(shuō),其主要是接收到 View 層發(fā)出的 Action,然后通過(guò)內(nèi)部操作,將 Action 轉(zhuǎn)換為 State。

以上,即為該 Reactor 所有的內(nèi)容,接下來(lái)我們逐步的定義、實(shí)現(xiàn)其全部?jī)?nèi)容。
Action: 描述用戶(hù)行為
我們一般會(huì)如何操作一張列表呢?無(wú)非是:
- 下拉 - 刷新拿到最新的數(shù)據(jù)
- 上拉 - 加載更多的數(shù)據(jù)
那么,以上兩個(gè)操作,即屬于用戶(hù)行為 (Action)
// 定義 Action
enum Action {
case loadFirstPage
case loadNextPage
}
Mutation: 用于描述狀態(tài)變更
對(duì)于以上兩個(gè)用戶(hù)行為,會(huì)有哪些狀態(tài)變更呢?
下拉行為:
- 變更1:觸發(fā)頂部刷新控件的狀態(tài)變更
- 變更2:觸發(fā)列表數(shù)據(jù)的狀態(tài)變更(拿到新一頁(yè)數(shù)據(jù)/沒(méi)有拿到最新數(shù)據(jù))
上拉行為:
- 變更1:觸發(fā)底部刷新控件的狀態(tài)變更
- 變更2:觸發(fā)列表數(shù)據(jù)的狀態(tài)變更(增多/不變)
// 定義 Mutation
enum Mutation {
case setLoadingFirstPage(Bool)
case fetchedNewestDatas([ListResponseData<HomeList>])
case setLoadingNextPage(Bool)
case fetchedMoreDatas([ListResponseData<HomeList>], nextPage: Int)
}
State:用于記錄當(dāng)前狀態(tài)
其用于記錄當(dāng)前狀態(tài):
- 顯示的數(shù)據(jù)
- 刷新?tīng)顟B(tài)(索取最新數(shù)據(jù))
- 刷新?tīng)顟B(tài)(索取更多數(shù)據(jù))
而當(dāng)前的狀態(tài)用于控制列表的顯示狀態(tài)
// 定義狀態(tài)
struct State {
var listDatas: [ListResponseData<HomeList>] = []
var isLoadingNewest: Bool = false
var isLoadingMore: Bool = false
var nextPage: Int?
}
Mutate() :處理 Action
我們需要處理所有定義的 Action(這里定義了兩個(gè) Action: loadFirstPage、loadNextPage)

// 方法1:將用戶(hù)行為轉(zhuǎn)換為顯示狀態(tài),并返回 Mutation 可觀察序列
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .loadFirstPage:
// 如果當(dāng)前正在刷新最新數(shù)據(jù),則不重復(fù)刷新
guard !self.currentState.isLoadingNewest else { return Observable.empty() }
return Observable.concat([
Observable.just(Mutation.setLoadingFirstPage(true)),
// 通過(guò)網(wǎng)絡(luò)服務(wù)類(lèi)(RequestService)索取網(wǎng)絡(luò)數(shù)據(jù),并處理成 Observable<Mutation> 類(lèi)型
RequestService.fetchListData(with: .home).flatMap({ (listData) -> Observable<Mutation> in
return Observable.just(Mutation.fetchedNewestDatas([listData]))
}),
Observable.just(Mutation.setLoadingFirstPage(false)),
])
case .loadNextPage:
guard let currentPage = self.currentState.nextPage, !self.currentState.isLoadingMore else { return Observable.empty() }
return Observable.concat([
Observable.just(Mutation.setLoadingNextPage(true)),
RequestService.fetchListData(with: .home, page: currentPage).flatMap({ (listData) -> Observable<Mutation> in
return Observable.just(Mutation.fetchedMoreDatas([listData], nextPage: currentPage + 1))
}),
Observable.just(Mutation.setLoadingNextPage(false)),
])
}
}
Reduce() :更新 State
拿到舊的狀態(tài)值,根據(jù)上一個(gè)操作返回的 Mutation 處理成新的 State
// 方法2:拿到方法1中的 Mutation ,更新?tīng)顟B(tài)
func reduce(state: State, mutation: Mutation) -> State {
// 拿到舊的狀態(tài)值
var newState = state
// 拿到上一步處理好的 mutation,協(xié)助更新 State 的值(總共有四種中間狀態(tài) - Mutation)
switch mutation {
case .setLoadingFirstPage(let isRefreshing):
newState.isLoadingNewest = isRefreshing
case .setLoadingNextPage(let loadingMore):
newState.isLoadingMore = loadingMore
case .fetchedNewestDatas(let newestDatas):
newState.listDatas = newestDatas
// 這里拿第2頁(yè)作為首頁(yè),故接下來(lái)應(yīng)該為第3頁(yè)的數(shù)據(jù)
newState.nextPage = 3
case let .fetchedMoreDatas(appendedDatas, nextPage: nextPage):
// 拿到下一頁(yè)數(shù)據(jù)之后,需要拼接到已請(qǐng)求到的數(shù)據(jù)之后
newState.listDatas.append(contentsOf: appendedDatas)
newState.nextPage = nextPage
}
return newState
}
OK,我們已經(jīng)構(gòu)建好 Reactor 類(lèi)了,接下來(lái)進(jìn)入主菜:構(gòu)造 UIViewController。
View(UIViewController && UIView)
ReactorKit 把 UIViewController 和 UIView 都當(dāng)成 View ,而它們主要是負(fù)責(zé)發(fā)出 Action,故我們需要監(jiān)測(cè) View 層發(fā)出的 Action。
控制器類(lèi)的選型
對(duì)于開(kāi)發(fā)控制器,一般有2種方式:
- 使用 Storyboard 開(kāi)發(fā)控制器
這種方式下,我們需要讓該控制器類(lèi)繼承 ReactorKit 的StoryboardView - 純代碼開(kāi)發(fā)控制器
這種方式下,我們需要讓該控制器類(lèi)繼承 ReactorKit 的 View
控制器類(lèi)的基本配置
配置1: 配置頂部控件
// 這是在上一篇文章中有說(shuō)過(guò)的內(nèi)容,感興趣可以去查看
fileprivate func initializeTopBarControls() {
let barStyle = NavigationBarStyle(center: (image: nil, title: "首頁(yè)"))
let navigationBar = NavigationBar(themeStyle: barStyle)
self.view.addSubview(navigationBar)
}
配置2:列表 CollectionView 的基本配置
// 因?yàn)槲覀兪鞘褂?Storyboard 進(jìn)行配置的,所以這里需要配置的屬性就很少
fileprivate func configure(for currentCollectionView: UICollectionView) {
currentCollectionView.registerForCell(HomeListCell.self)
}
配置3:定義列表的數(shù)據(jù)源屬性
let dataSource = RxCollectionViewSectionedReloadDataSource<ListResponseData<HomeList>>()
在控制器類(lèi)中使用 ReactorKit
第1步:引進(jìn)該框架
import ReactorKit
第2步:指定 Reactor 的類(lèi)型
// 這里是首頁(yè)模塊,故其類(lèi)型為 HomePageReactor
typealias Reactor = HomePageReactor
第3步:實(shí)現(xiàn)協(xié)議屬性
var disposeBag = DisposeBag()
第4步:注入 Reactor
if let homeViewController = homeNav.viewControllers.first as? HomeViewController {
// 必須先注入 Reactor 類(lèi),注入之后, ReactorKit 自動(dòng)回調(diào)用第5步的綁定方法
homeViewController.reactor = HomePageReactor()
}
第5步:實(shí)現(xiàn)協(xié)議方法
func bind(reactor: Reactor) {
// 待會(huì)會(huì)在這里搞事情,請(qǐng)期待...
}
處理綁定事件
在處理綁定事件前,我們先設(shè)置 UICollectionView 的代理和數(shù)據(jù)源方法
// DataSource && Delegate
collectionView.rx.setDelegate(self).disposed(by: disposeBag)
self.dataSource.configureCell = { _, collectionView, indexPath, element in
let cell = collectionView.dequeueCell(HomeListCell.self, indexPath: indexPath)
// 設(shè)置 Cell 的響應(yīng)器(Cell 也是 View 層,其中的處理與當(dāng)前控制器類(lèi)是一樣的,這里不再贅余)
cell.reactor = HomeListCellReactor(data: element)
cell.feedNumber = indexPath.item + 1
return cell
}
在以上第5步的綁定方法中,我們需要去監(jiān)測(cè) View 層的 Action,只有我們定義的ObserVable Sequence 中有 Action 發(fā)出,我們的 Reactor 就拿到該 Action 進(jìn)行相應(yīng)的處理
// Action(View -> Reactor)
// 處理加載第一頁(yè)數(shù)據(jù)的 Action
collectionView.rx.contentOffset
.filter { [weak self] offset in
guard let strongSelf = self else { return false }
guard strongSelf.collectionView.height > 0 else { return false }
return ((offset.y < Constant.refreshTriggerValue || strongSelf.collectionView.contentSize.height == 0) ? true : false)
}
.map { _ in Reactor.Action.loadFirstPage }
.bind(to: reactor.action)
.disposed(by: self.disposeBag)
// 處理加載更多的 Action
// Note: 這里我們可以進(jìn)行數(shù)據(jù)的預(yù)加載,通過(guò)控制 UICollectionView 的 OffSet,
// 當(dāng)其快滑到當(dāng)前列表的底部時(shí),我們先進(jìn)行數(shù)據(jù)的加載,沒(méi)必要等到列表完全加載完才去加載數(shù)據(jù)
collectionView.rx.contentOffset
.filter { [weak self] offset in
guard let strongSelf = self else { return false }
guard strongSelf.collectionView.contentSize.height > 0 else { return false }
return (offset.y + strongSelf.collectionView.height + 50 > strongSelf.collectionView.contentSize.height ? true : false)
}
.map { _ in Reactor.Action.loadNextPage }
.bind(to: reactor.action)
.disposed(by: self.disposeBag)
而當(dāng) Reactor 處理好 Action 之后,會(huì)有新的 state,這個(gè)時(shí)候,我們就需要去監(jiān)聽(tīng)新的 state,將其綁定到 UICollectionView 的數(shù)據(jù)源上,而當(dāng)數(shù)據(jù)源發(fā)生變化時(shí),RxSwift 又會(huì)去處理 dataSource 的
configureCell 方法,實(shí)現(xiàn)數(shù)據(jù)的良性傳輸。
// State(Reactor -> View)
reactor.state.asObservable()
.map { $0.listDatas }
.bind(to: self.collectionView.rx.items(dataSource: self.dataSource))
.disposed(by: self.disposeBag)
到這里,我們就已成功的使用 RxSwift + ReactorKit 構(gòu)建了一個(gè)信息流框架。
如果感興趣的話(huà),無(wú)論你們的項(xiàng)目現(xiàn)在或?qū)?lái)會(huì)不會(huì)使用到這個(gè)技術(shù)點(diǎn),都不妨親手試試,一定會(huì)有不少收獲的!
Demo: https://github.com/iJudson/RxSwift-ReactorKit
歡迎 stars
Thanks:多謝觀看,歡迎收藏文章,歡迎關(guān)注、交流...