RxSwift + ReactorKit 构建信息流框架”> RxSwift +
需要实现的效果(动态图)
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 ,还是一直存在几个困惑:
其实对于以上问题,我曾经想过使用面向协议编程,让一些重复使用到的代码,或者像一些列表类必须实现的方法定义成协议,让类去遵循协议,实现协议方法,进一步让该类的实现更加合理化,条理更加清晰。但是,不可避免的要定义很多协议,实现很多协议方法…
那么,有没有一个框架,可以进一步增强 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 所有属性和方法
以上,即为该 Reactor 所有的内容,接下来我们逐步的定义、实现其全部内容。
Action: 描述用户行为
我们一般会如何操作一张列表呢?无非是:
那么,以上两个操作,即属于用户行为 (Action)
// 定义 Action
enum Action {
case loadFirstPage
case loadNextPage
}
Mutation: 用于描述状态变更
对于以上两个用户行为,会有哪些状态变更呢?
下拉行为:
上拉行为:
// 定义 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)
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种方式:
控制器类的基本配置
配置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:
欢迎 stars
Thanks:多谢观看,欢迎收藏文章,欢迎关注、交流…
免责声明:本文系转载自其它媒体,版权归原作者所有;本站遵循行业规范,任何转载的稿件都会明确标注作者和来源,旨在传递信息,不代表本站的观点、立场和对其真实性负责。如需转载,请联系原作者。内容会稍有编辑,如果来源标注有误或侵犯了您的合法权益等其他原因不想在本站发布,来信即删。投稿等其它问题请联系本站
文章来源:https://www.jianshu.com/p/dff7b0368d2b
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。