<荐> RxSwift + ReactorKit 构建信息流框架

需要实现的效果(动态图)

Note: 以上即为我们需要实现的效果,可以在 RxBasicInterface 拿到基本框架的代码直接着手开发。如果你对这个使用 RxSwift 实现的基本框架感兴趣的话,可以查看文章: Rx 项目基本框架的构建 ,而当前最终代码: 信息流 Demo。

在还没遇到 ReactorKit 这个框架之前,我使用 RxSwift + MVVM 去构建如图的信息流时,确实为我带来很多好处:

  • 层级更加清晰,分工和职能更加明确
  • 大幅度解耦控制器类,代码的逻辑体现更为明朗、易看
  • 响应式编程,杂散的代码块转化为了集中的单向代码流
  • 可维护性和可测试性都极大的增强
  • 不再增加一些所谓的语法糖,代码也很 ''甜''
    ....

确实是这样子,没使用 RxSwift + MVVM 时,我在代码中使用了挺多语法糖的,举个 NotificationCenter 使用语法糖的例子:

// 在需使用到 Selector 类前添加以下代码
private extension Selector {
    static let handleFunction = #selector(ExampleViewController.handleFunction)
}

// 在类中的使用就变得很简单
NotificationCenter.default.addObserver(self, selector: .handleFunction, name: .notificationName, object: nil)

其实,以上的语法糖,我在使用 RxSwift 之前,确实觉得它让代码看起来更好看了些。但在使用 RxSwift 之后,真心觉得,使用这个语法糖,就是在每个类前面加了顶绿帽子,只是增长了代码的长度而已...
而使用 RxSwift ,我们不再需要添加这个语法糖了。

但是,使用 RxSwift + MVVM ,还是一直存在几个困惑:

  • 我需要在 ViewModel、Service 类中添加多个 Observer、Observable,而有些 Observer 又得是 Observable,这样一来,我们需要额外的去注意这些属性的应用场合
  • ViewModel 作为其中主要的处理器,如果其控制器类比较复杂,那么它就需要管理很多属性,极大的增加了我们的使用负担
  • 代码复用率比较低

其实对于以上问题,我曾经想过使用面向协议编程,让一些重复使用到的代码,或者像一些列表类必须实现的方法定义成协议,让类去遵循协议,实现协议方法,进一步让该类的实现更加合理化,条理更加清晰。但是,不可避免的要定义很多协议,实现很多协议方法...
那么,有没有一个框架,可以进一步增强 RxSwfit + MVVM 的优势,削减其劣势呢?
直到遇到了 ReactorKit 这个框架,解决了我所有的困惑。

认识 ReactorKit

考察
ReactorKit 是 Jeon Suyeol 的作品,而
Jeon Suyeol 发布了很多富有创造性的框架,如 Then,URLNavigator,SwiftyImage 以及一些开源项目 RxTodo,Drrrible。同时,他也是多个组织的成员( RxSwiftCommunity,Moya,SwiftKorea...),所以我们完全可以放心的使用这个框架,完全不需要去担心这个框架后期维护的问题。其实这个框架的思想并不复杂,即使 Jeon Suyeol 不再维护该框架,我们也完全可以按照他的思想,写个类似的框架供自己使用。

优点

  • 分工、职能更进一步清晰、明朗
  • 更进一步的模块化和响应式,让代码更便于管理
  • 可由我们熟悉的 用户行为 折射到界面熟悉的 状态行为

使用 ReactorKit

使用介绍

ReactorKit 是一个轻量的响应式编程框架,我们把所有的视图(UIView)、界面(UIViewController)都当成 View,而 View 主要是被用户直接操作的层级,我们通过监测用户在 View 上的行为,反馈给 Reactor(响应器),经由响应器处理之后把响应状态传递给 View 层。最后, View 显示最终的传递的状态;简单来说,即 View 层只发出行为,而 Reactor 只发出状态,相互把对方所需要的东西传递给对方,构成一条响应式的序列。

<荐> RxSwift + ReactorKit 构建信息流框架_第1张图片
响应过程

那么,我们先从响应器 Reactor 着手,先分析用户行为,再在其中将用户行为转换为可呈现在 View 上的 State。

Reactor

对于响应器来说,其主要是接收到 View 层发出的 Action,然后通过内部操作,将 Action 转换为 State。

<荐> RxSwift + ReactorKit 构建信息流框架_第2张图片
Reactor 所有属性和方法

以上,即为该 Reactor 所有的内容,接下来我们逐步的定义、实现其全部内容。

Action: 描述用户行为

我们一般会如何操作一张列表呢?无非是:

  • 下拉 - 刷新拿到最新的数据
  • 上拉 - 加载更多的数据

那么,以上两个操作,即属于用户行为 (Action)

// 定义 Action
enum Action {
    case loadFirstPage
    case loadNextPage
}
Mutation: 用于描述状态变更

对于以上两个用户行为,会有哪些状态变更呢?
下拉行为:

  • 变更1:触发顶部刷新控件的状态变更
  • 变更2:触发列表数据的状态变更(拿到新一页数据/没有拿到最新数据)

上拉行为:

  • 变更1:触发底部刷新控件的状态变更
  • 变更2:触发列表数据的状态变更(增多/不变)
// 定义 Mutation
enum Mutation {
    case setLoadingFirstPage(Bool)
    case fetchedNewestDatas([ListResponseData])
    case setLoadingNextPage(Bool)
    case fetchedMoreDatas([ListResponseData], nextPage: Int)
}
State:用于记录当前状态

其用于记录当前状态:

  • 显示的数据
  • 刷新状态(索取最新数据)
  • 刷新状态(索取更多数据)

而当前的状态用于控制列表的显示状态

// 定义状态
struct State {
    var listDatas: [ListResponseData] = []
    var isLoadingNewest: Bool = false
    var isLoadingMore: Bool = false
    var nextPage: Int?
}
Mutate() :处理 Action

我们需要处理所有定义的 Action(这里定义了两个 Action: loadFirstPage、loadNextPage)

<荐> RxSwift + ReactorKit 构建信息流框架_第3张图片
Mutate 数据处理过程
// 方法1:将用户行为转换为显示状态,并返回 Mutation 可观察序列
func mutate(action: Action) -> Observable {
    
    switch action {
    case .loadFirstPage:
        // 如果当前正在刷新最新数据,则不重复刷新
        guard !self.currentState.isLoadingNewest else { return Observable.empty() }
        return Observable.concat([
            Observable.just(Mutation.setLoadingFirstPage(true)),
            // 通过网络服务类(RequestService)索取网络数据,并处理成 Observable 类型
            RequestService.fetchListData(with: .home).flatMap({ (listData) -> Observable 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 in
                return Observable.just(Mutation.fetchedMoreDatas([listData], nextPage: currentPage + 1))
            }),
            
            Observable.just(Mutation.setLoadingNextPage(false)),
            
            ])
    }
}
Reduce() :更新 State

拿到旧的状态值,根据上一个操作返回的 Mutation 处理成新的 State

// 方法2:拿到方法1中的 Mutation ,更新状态
func reduce(state: State, mutation: Mutation) -> State {
   // 拿到旧的状态值
   var newState = state
   // 拿到上一步处理好的 mutation,协助更新 State 的值(总共有四种中间状态 - 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页作为首页,故接下来应该为第3页的数据
       newState.nextPage = 3
       
   case let .fetchedMoreDatas(appendedDatas, nextPage: nextPage):
       // 拿到下一页数据之后,需要拼接到已请求到的数据之后
       newState.listDatas.append(contentsOf: appendedDatas)
       newState.nextPage = nextPage
   }

   return newState
}

OK,我们已经构建好 Reactor 类了,接下来进入主菜:构造 UIViewController。

View(UIViewController && UIView)

ReactorKit 把 UIViewController 和 UIView 都当成 View ,而它们主要是负责发出 Action,故我们需要监测 View 层发出的 Action。

控制器类的选型

对于开发控制器,一般有2种方式:

  • 使用 Storyboard 开发控制器
    这种方式下,我们需要让该控制器类继承 ReactorKit 的StoryboardView
  • 纯代码开发控制器
    这种方式下,我们需要让该控制器类继承 ReactorKit 的 View
控制器类的基本配置

配置1: 配置顶部控件

// 这是在上一篇文章中有说过的内容,感兴趣可以去查看
fileprivate func initializeTopBarControls() {
    let barStyle = NavigationBarStyle(center: (image: nil, title: "首页"))
    let navigationBar = NavigationBar(themeStyle: barStyle)
    self.view.addSubview(navigationBar)
}

配置2:列表 CollectionView 的基本配置

// 因为我们是使用 Storyboard 进行配置的,所以这里需要配置的属性就很少
fileprivate func configure(for currentCollectionView: UICollectionView) {
    currentCollectionView.registerForCell(HomeListCell.self)
}

配置3:定义列表的数据源属性

let dataSource = RxCollectionViewSectionedReloadDataSource>()
在控制器类中使用 ReactorKit

第1步:引进该框架

import ReactorKit

第2步:指定 Reactor 的类型

// 这里是首页模块,故其类型为 HomePageReactor
typealias Reactor = HomePageReactor

第3步:实现协议属性

var disposeBag = DisposeBag()

第4步:注入 Reactor

if let homeViewController = homeNav.viewControllers.first as? HomeViewController {
    // 必须先注入 Reactor 类,注入之后, ReactorKit 自动回调用第5步的绑定方法
    homeViewController.reactor = HomePageReactor()
}

第5步:实现协议方法

func bind(reactor: Reactor) {
  // 待会会在这里搞事情,请期待...
}
处理绑定事件

在处理绑定事件前,我们先设置 UICollectionView 的代理和数据源方法

// DataSource && Delegate
 collectionView.rx.setDelegate(self).disposed(by: disposeBag)

 self.dataSource.configureCell = { _, collectionView, indexPath, element in
    let cell = collectionView.dequeueCell(HomeListCell.self, indexPath: indexPath)
    // 设置 Cell 的响应器(Cell 也是 View 层,其中的处理与当前控制器类是一样的,这里不再赘余)
    cell.reactor = HomeListCellReactor(data: element)
    cell.feedNumber = indexPath.item + 1
    return cell
 }

在以上第5步的绑定方法中,我们需要去监测 View 层的 Action,只有我们定义的ObserVable Sequence 中有 Action 发出,我们的 Reactor 就拿到该 Action 进行相应的处理

// Action(View -> Reactor)
  // 处理加载第一页数据的 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: 这里我们可以进行数据的预加载,通过控制 UICollectionView 的 OffSet,
  // 当其快滑到当前列表的底部时,我们先进行数据的加载,没必要等到列表完全加载完才去加载数据
 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)

而当 Reactor 处理好 Action 之后,会有新的 state,这个时候,我们就需要去监听新的 state,将其绑定到 UICollectionView 的数据源上,而当数据源发生变化时,RxSwift 又会去处理 dataSource 的
configureCell 方法,实现数据的良性传输。

// State(Reactor -> View)
 reactor.state.asObservable()
    .map { $0.listDatas }
    .bind(to: self.collectionView.rx.items(dataSource: self.dataSource))
    .disposed(by: self.disposeBag)

到这里,我们就已成功的使用 RxSwift + ReactorKit 构建了一个信息流框架。
如果感兴趣的话,无论你们的项目现在或将来会不会使用到这个技术点,都不妨亲手试试,一定会有不少收获的!
Demo: https://github.com/iJudson/RxSwift-ReactorKit
欢迎 stars
Thanks:多谢观看,欢迎收藏文章,欢迎关注、交流...

你可能感兴趣的:(<荐> RxSwift + ReactorKit 构建信息流框架)