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 只发出状态,相互把对方所需要的东西传递给对方,构成一条响应式的序列。
那么,我们先从响应器 Reactor 着手,先分析用户行为,再在其中将用户行为转换为可呈现在 View 上的 State。
Reactor
对于响应器来说,其主要是接收到 View 层发出的 Action,然后通过内部操作,将 Action 转换为 State。
以上,即为该 Reactor 所有的内容,接下来我们逐步的定义、实现其全部内容。
Action: 描述用户行为
我们一般会如何操作一张列表呢?无非是:
- 下拉 - 刷新拿到最新的数据
- 上拉 - 加载更多的数据
那么,以上两个操作,即属于用户行为 (Action)
// 定义 Action
enum Action {
case loadFirstPage
case loadNextPage
}
Mutation: 用于描述状态变更
对于以上两个用户行为,会有哪些状态变更呢?
下拉行为:
- 变更1:触发顶部刷新控件的状态变更
- 变更2:触发列表数据的状态变更(拿到新一页数据/没有拿到最新数据)
上拉行为:
- 变更1:触发底部刷新控件的状态变更
- 变更2:触发列表数据的状态变更(增多/不变)
// 定义 Mutation
enum Mutation {
case setLoadingFirstPage(Bool)
case fetchedNewestDatas([ListResponseData<HomeList>])
case setLoadingNextPage(Bool)
case fetchedMoreDatas([ListResponseData<HomeList>], nextPage: Int)
}
State:用于记录当前状态
其用于记录当前状态:
- 显示的数据
- 刷新状态(索取最新数据)
- 刷新状态(索取更多数据)
而当前的状态用于控制列表的显示状态
// 定义状态
struct State {
var listDatas: [ListResponseData<HomeList>] = []
var isLoadingNewest: Bool = false
var isLoadingMore: Bool = false
var nextPage: Int?
}
Mutate() :处理 Action
我们需要处理所有定义的 Action(这里定义了两个 Action: loadFirstPage、loadNextPage)
// 方法1:将用户行为转换为显示状态,并返回 Mutation 可观察序列
func mutate(action: Action) -> Observable<Mutation> {
switch action {
case .loadFirstPage:
// 如果当前正在刷新最新数据,则不重复刷新
guard !self.currentState.isLoadingNewest else { return Observable.empty() }
return Observable.concat([
Observable.just(Mutation.setLoadingFirstPage(true)),
// 通过网络服务类(RequestService)索取网络数据,并处理成 Observable<Mutation> 类型
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
拿到旧的状态值,根据上一个操作返回的 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<ListResponseData<HomeList>>()
在控制器类中使用 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:多谢观看,欢迎收藏文章,欢迎关注、交流...