FSA全栈行动 FSA全栈行动
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr

公众号:FSA全栈行动

记录学习过程中的知识
首页
  • 移动端文章

    • Android
    • iOS
    • Flutter
  • 学习笔记

    • 《Kotlin快速入门进阶》笔记
    • 《Flutter从入门到实战》笔记
    • 《Flutter复习》笔记
前端
后端
  • 学习笔记

    • 《深入浅出设计模式Java版》笔记
  • 逆向
  • 分类
  • 标签
  • 归档
  • LinXunFeng
  • GitLqr
  • AndroidUI

  • Android第三方SDK

  • Android混淆

  • Android仓库

  • Android新闻

  • Android系统开发

  • Android源码

  • Android注解AOP

  • Android脚本

  • AndroidTv开发

  • AndroidNDK

  • Android音视频

  • Android热修复

  • Android性能优化

  • Android云游戏

  • Android插件化

  • iOSUI

  • iOS工具

  • iOS底层原理与应用

  • iOS组件化

  • iOS音视频

  • iOS疑难杂症

  • iOS之Swift

  • iOS之RxSwift

  • iOS开源项目

    • iOS - Swift3.0 高仿喜马拉雅FM
    • iOS - Swift 高仿微信
    • iOS - 面向协议方式封装空白页功能
    • iOS - 打造Moya便捷解析库,提供RxSwift拓展
    • iOS - 面向协议封装全屏旋转功能
    • Swift - 优雅的适配大小
    • Swift - 掌控Moya的网络请求、数据解析与缓存
    • ReactorKit + RxDataSources列表多次刷新的解决方案
      • 一、常规使用
      • 二、优化
      • 三、改进
      • 四、使用
  • iOS逆向

  • Flutter开发

  • 移动端
  • iOS开源项目
LinXunFeng
2021-12-08
目录

ReactorKit + RxDataSources列表多次刷新的解决方案

欢迎关注微信公众号:[FSA全栈行动 👋]

相信使用 ReactorKit + RxDataSources 的同学都有遇到列表会多次刷新的问题吧,本篇将提出我的解决方案,相互学习交流

# 一、常规使用

Reactor

enum Mutation {
    case setSections([LXFSection])
    ...
}

struct State {
    var sections : [LXFSection] = []
    ...
}

View

reactor.state.map { $0.sections }
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)

由于在 ReactorKit 中,View 对状态的订阅都是针对 State 来说的,而非 State 中的属性,所以只要 State 的值发生改变,View 中所有对 State 的订阅回调都会被调用,从而使视图进行更新。

但是一个属性的变化,理应只需要更新对应的视图即可,要达到这一效果,就需要使用 distinctUntilChanged 这个方法,对于 State 中遵守了 Equatable 协议的属性,直接加上 .distinctUntilChanged() 即可

但是看我们定义的 Section

import RxDataSources

enum LXFSection {
    case list([LXFSectionItem])
}

extension LXFSection: SectionModelType {
    init(original: LXFSection, items: [LXFSectionItem]) {
        switch original {
        case .list: self = .list(items)
        }
    }
    
    var items: [LXFSectionItem] {
        switch self {
        case .list(let items): return items
        }
    }
}

enum LXFSectionItem {
    case item(LXFCellReactor)
}

需要自己给 LXFSection 遵守 Equatable 并实现协议方法,或者是在 distinctUntilChanged 回调中自己做判断 ,但都太麻烦了~

reactor.state.map { $0.sections }
    .distinctUntilChanged({ (sectionArr1: [LXFSection], sectionArr2: [LXFSection]) in
        return 判断 sectionArr1 和 sectionArr2 是否相等
    })
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)

那有没有其它办法呢?答案当然是有的

# 二、优化

创建一个名为 SwiftyTraceableValue 的结构体,定义如下:

public struct SwiftyTraceableValue<T> {
    public var tracker: Int = 0
    public var value: T
}

内部属性说明:

属性 作用
tracker 用于判断是否数据是否有发生变化
value 存储原来的值

调整 Mutation

enum Mutation {
    case setSections(TraceableValue<[LXFSection]>)
    ...
}

调整 State

struct State {
    var sections : TraceableValue<[LXFSection]> = .init(value: [])
    ...
}

调整 Sections 数据的处理

let oldTracker = self?.currentState.sections.tracker ?? 0
let oldSections = self?.currentState.sections.value ?? []
items = oldSections.first?.items ?? [] + items
let finalSections = [LXFSection.list(items)]
let sections = TraceableValue(tracker: oldTracker + 1, value: finalSections)
let setSections = Observable.just(Mutation.setSections(sections))

调整 View

reactor.state.map { $0.sections }
	.distinctUntilChanged {
        $0.tracker == $1.tracker
    }
    .map { $0.value }
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)

至此,按如上的内容进行调整便可以解决列表多次刷新的问题,但是这改动也忒大了,而且还要自己管理 tracker 的值,这怎么能忍?

那有什么办法可以让我们不再手动管理 tracker 呢?🤔

这里我们可以使用 Swift 的 Property Wrapper😏

# 三、改进

我们将 SwiftyTraceableValue 使用 Property Wrapper 进行封装

@propertyWrapper
public struct SwiftyTraceableValue<T> {
    public  var tracker: Int = 0
    public var value: T
    
    public var projectedValue: SwiftyTraceableValue {
        return self
    }
    
    public var wrappedValue: T {
        get {
            return self.value
        } set {
            self.tracker += 1
            self.value = newValue
        }
    }
    
    public init(wrappedValue: T) {
        self.value = wrappedValue
    }
}

这样,每次被赋值时即可自动为 tracker 加 1。这里 projectedValue 我们返回真实类型 SwiftyTraceableValue ,以便后续使用。

这里简单提一下属性包装器的使用,更加具体详细的内容可以看我另一篇文章《Swift - PropertyWrapper》

// 使用 @SwiftyTraceableValue 对属性进行修饰
@SwiftyTraceableValue var sections : [LXFSection] = []

// 获取 wrappedValue 的值
sections

// 获取 projectedValue 的值
$sections

不仅如此,我们还为 SwiftyTraceableValue 专门扩展 ObservableType,将进行比较内容部分封装起来

public extension ObservableType {
    func mapDistinctUntilTraceableValueChanged<T>(
        _ transform: @escaping (Element) throws -> SwiftyTraceableValue<T>
    ) -> Observable<T> {
        return self
            .map(transform)
            .distinctUntilChanged { $0.tracker == $1.tracker }
            .map { $0.value }
    }
}

大功造成,接下来看看如何使用吧

# 四、使用

以下内容是基于第一部分进行调整的,大家可以忘掉第二部分的修改了

最终我们只需要调整两处地方即可解决列表多次刷新的问题!

Reactor

struct State {
    @SwiftyTraceableValue var sections : [LXFSection] = []
    ...
}

View

reactor.state.mapDistinctUntilTraceableValueChanged { $0.$sections }
    .bind(to: tableView.rx.items(dataSource: dataSource))
    .disposed(by: disposeBag)

现在,我将它做成了开源库,方便大家使用,好用的话请 Star 吧 😃

GitHub: SwiftyTraceableValue (opens new window)

#iOS#Swift#RxSwift#ReactorKit
Swift - 掌控Moya的网络请求、数据解析与缓存
Mac远程登录到iOS设备

← Swift - 掌控Moya的网络请求、数据解析与缓存 Mac远程登录到iOS设备→

最近更新
01
Flutter - Xcode16 还原编译速度
04-05
02
AI - 免费的 Cursor 平替方案
03-30
03
Android - 2025年安卓真的闭源了吗
03-28
更多文章>
Theme by Vdoing | Copyright © 2020-2025 FSA全栈行动
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式
×