<薦> RxSwift + ReactorKit 構(gòu)建信息流框架

需要實(shí)現(xiàn)的效果(動(dòng)態(tài)圖)

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è)組織的成員( RxSwiftCommunityMoya,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)過(guò)程

那么,我們先從響應(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 所有屬性和方法

以上,即為該 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)

Mutate 數(shù)據(jù)處理過(guò)程
// 方法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ì)有不少收獲的!
Demohttps://github.com/iJudson/RxSwift-ReactorKit
歡迎 stars
Thanks:多謝觀看,歡迎收藏文章,歡迎關(guān)注、交流...

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,324評(píng)論 25 708
  • Spring Cloud為開(kāi)發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見(jiàn)模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,695評(píng)論 19 139
  • 國(guó)家電網(wǎng)公司企業(yè)標(biāo)準(zhǔn)(Q/GDW)- 面向?qū)ο蟮挠秒娦畔?shù)據(jù)交換協(xié)議 - 報(bào)批稿:20170802 前言: 排版 ...
    庭說(shuō)閱讀 12,523評(píng)論 6 13
  • “我終于拿到第一筆提成了!” 表妹興奮地歡呼到。這是她12月份新找到的一家公司,應(yīng)聘時(shí)說(shuō)她作為實(shí)習(xí)生是沒(méi)有提出可拿...
    我是吳掌柜閱讀 293評(píng)論 0 0
  • 講師:Allan Adams 授課語(yǔ)言:英文 類(lèi)型:演講 TED全網(wǎng)首播 科技 課程簡(jiǎn)介:2015年9月14日是一...
    TED精選集閱讀 400評(píng)論 0 0

友情鏈接更多精彩內(nèi)容