<荐> RxSwift + ReactorKit 构

作者: 灵度Ling | 来源:发表于2017-10-03 15:10 被阅读1217次
    需要实现的效果(动态图)
    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 发布了很多富有创造性的框架,如 ThenURLNavigatorSwiftyImage 以及一些开源项目 RxTodoDrrrible。同时,他也是多个组织的成员( RxSwiftCommunityMoyaSwiftKorea...),所以我们完全可以放心的使用这个框架,完全不需要去担心这个框架后期维护的问题。其实这个框架的思想并不复杂,即使 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: 用于描述状态变更

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

    • 变更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)

    Mutate 数据处理过程
    // 方法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 构建了一个信息流框架。
    如果感兴趣的话,无论你们的项目现在或将来会不会使用到这个技术点,都不妨亲手试试,一定会有不少收获的!
    Demohttps://github.com/iJudson/RxSwift-ReactorKit
    欢迎 stars
    Thanks:多谢观看,欢迎收藏文章,欢迎关注、交流...

    相关文章

      网友评论

        本文标题:<荐> RxSwift + ReactorKit 构

        本文链接:https://www.haomeiwen.com/subject/tdfaextx.html