「SectionReactor」RxSwift 下 Cell 中的事件通知 TableView / CollectionView 的刷新

在使用 RxSwift 时,在一个页面中,经常会遇到这样的情况,在 Cell 中的事件需要通知 TableView / CollectionView 的刷新,那整个的事件流和数据的传递是这样的么?

SectionReactor 提供了一种解决方案,并提供了一个 Demo,可供我们学习。

项目中有下面的文章列表:ArticleListView,展示试了文章内容和评论。

image

当点击 Create a comment 的时,会在文章下面添加一行评论。

我们首先来拆解 ArticleListView 页面:

  • 首先是一个 TableView 作为父控件,承载着所有的内容
  • 单篇文章的内容作为一个 Section,其中的作者信息、文章内容、创建评论的按钮和评论使用不同的 Cell 来展示。

ArticleListView 对应的 Reactor 为 ArticleListViewReactor,该 Reactor 的 State 当中有用于控制 TableView`s Section 的 sections 属性。单篇文章对应的 Section 中的 Cell 应该如何展示,由 ArticleSectionReactor 控制。

也就是说:

  • ArticleListViewReactor 控制着 TableView 的 Section 的数量
  • ArticleSectionReactor 控制着 TableView 中单个 Section 中的 Cell 的数量和形式

下面是 ArticleListViewReactor 的 State 的定义:

final class ArticleListViewReactor: Reactor {
    
    struct State {
        var isRefreshing: Bool = false
        fileprivate var articleSectionReactors: [ArticleSectionReactor] = []
        var sections: [ArticleListViewSection] {
            return self.articleSectionReactors.map(ArticleListViewSection.article)
        }
    }
}

接下来梳理一下当点击 Create a comment 按钮之后的数据流是怎样的。

评论的产生

Create a comment 按钮所在的 Cell,在点击按钮之后,会发出 createComment 的事件,进而进行相关的操作,生成新的评论,并通过 Comment.event 发出新的评论信息。

func mutate(action: Action) -> Observable<Mutation> {
    switch action {
    case .createComment:
        return Observable.create { [weak self] observer in
            if let articleID = self?.currentState.articleID {
                let comment = Comment.random(articleID: articleID)
                Comment.event.onNext(.create(comment))
            }
            observer.onCompleted()
            return Disposables.create()
        }
    }
}

评论的接收

ArticleSectionReactor 由一个文章数据初始化生成,它监听了 Comment.event 的事件。在接收到 Comment.event 事件时会对当前的文章数据进行修改:

func transform(mutation: Observable<Mutation>) -> Observable<Mutation> {
    let fromCommentEvent = Comment.event.flatMap { [weak self] event -> Observable<Mutation> in
        guard let self = self else { return .empty() }
        switch event {
        case let .create(comment):
            guard comment.articleID == self.currentState.article.id else { return .empty() }
            return .just(Mutation.appendComment(comment))
        }
    }
    return Observable.merge(mutation, fromCommentEvent)
}

TableView 的刷新

ArticleListViewReactor 通知 TableView 刷新的时机有两个:

  1. 当 state 的 sections 属性改变的时候需要刷新。因为 sections 的属性是只读属性,随着 articleSectionReactors 的改变而改变。所以 articleSectionReactors 数组改变时需要刷新。

  2. articleSectionReactors 数组中的每个 ArticleSectionReactor 控制着一个 section 的内容,所以 ArticleSectionReactor 的 state 中的 sectionItems 改变时需要刷新。

其中第一个条件比较容易满足,state 中的 articleSectionReactors 数组改变时,就会生成一个新的 State,TableView 就可以进行刷新了。

但是第二个条件,ArticleListViewReactor 应该如何监听到 ArticleSectionReactor 中 sectionItems 的改变呢?

具体实现

为此,作者专门设计了 SectionReactor 框架。通过下面一行代码就可以监听到 articleSectionReactors 数组中各个 Reactor 的改变了。

func transform(state: Observable<State>) -> Observable<State> {
    return state.with(section: \.articleSectionReactors)
}

实现方式就将 state 的改变和该 state 中 sectionReactors 数组属性中 Reactor 的 state 改变的事件进行了合并。 上面代码中 \.articleSectionReactors 是指明 state 中 sectionReactors 的属性路径。

继续看具体的实现方法:

public extension ObservableType {
    
    // 当 state 改变或者 sectionReactors 的 state 改变了,都会发送 state 的改变信息,进而通知 View 进行刷新
    func with<State, Section>(
        section sectionReactorsClosure: @escaping (State) -> [Section]?
    ) -> Observable<State> where Element == State, Section: SectionReactor {
        return self.flatMapLatest { state -> Observable<Element> in
            guard let sectionReactors = sectionReactorsClosure(state) else { return .just(state) }
            // sectionStates 有任何变动的时候,都会发出 state
            
            let sectionStates = Observable.merge(sectionReactors.map { $0.state.skip(1) })
            return Observable.merge(.just(state), sectionStates.map { _ in state })
        }
    }
    
    func with<State, Section>(
        section sectionReactorClosure: @escaping (State) -> Section?
    ) -> Observable<State> where Element == State, Section: SectionReactor {
        return self.with(section: { state in sectionReactorClosure(state).map { [$0] } })
    }
}

将 State 的事件流转化成两个数据流的合并:

  • State 的数据流
  • State 的属性 sectionReactors 数组中的 Reactor 的 State 的数据流

当这两个数据流发生改变时,都会发出 State 的状态改变。

注意:flatMapLatest 的使用

将两个数据流进行合并的时候,注意是使用的 flatMapLatest。

flatMapLatest 操作符将源 Observable 的每一个元素应用一个转换方法,生成一个新的 Observable。一旦一个元素转换出一个新的 Observable,就只发出这个新转化而成的 Observable 产生的元素,在此之前转化生成的 Observable 发出的元素将被忽略掉。

因为 State 的属性 sectionReactors 数组中的 Reactor 在进行方法操作的时候可能是异步的。在 State 发生改变之后,应该忽略掉之前的事件,所以此处使用的是:flatMapLatest

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,287评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,346评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,277评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,132评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,147评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,106评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,019评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,862评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,301评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,521评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,682评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,405评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,996评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,651评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,803评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,674评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,563评论 2 352

推荐阅读更多精彩内容