RxSwift VM中的输入,输出

使用 RxSwift ,代码可读性很好

声明式编程,把要做的,一行行写下

看代码,像看爽文

定义都是静态的,不可变的

关注的是数据的变化。

需要良好的设计。

平常写 OOP, 哪里改,写那里。增补一番

写 FRP,指定位置,统一前置了

下面的例子是,一个搜索列表的功能

代码简练,不多

let API = DefaultWikipediaAPI.sharedAPI

        let results = searchBar.rx.text.orEmpty // 搜索框的文本
            .asDriver() 
            .throttle(.milliseconds(300))   // 300 毫秒内的事件,只取一个
            .distinctUntilChanged()   // 不变化,就不用管
            .flatMapLatest { query in    // 文本,映射成网络查询
                API.getSearchResults(query)    // 去查询
                    .retry(3)                               
                    // 失败了,重试三次
                    .retryOnBecomesReachable([], reachabilityService: Dependencies.sharedDependencies.reachabilityService)    
                    //   有网,才重试
                    .startWith([])    //  先清空,再出结果
                    .asDriver(onErrorJustReturn: [])                               
                    // 出错,返回 []
            }
            .map { results in
                results.map(SearchResultViewModel.init)         
                // Json 转 Model
            }

        results
            .drive(resultsTableView.rx.items(cellIdentifier: "WikipediaSearchCell", cellType: WikipediaSearchCell.self)) { (_, viewModel, cell) in    
                 // Model 出界面
                cell.viewModel = viewModel
            }
            .disposed(by: disposeBag)                                              
            // 界面退出,disposeBag 销毁, 订阅销毁

RxSwift 给了开发者很好的基础设施

  • 可以用同步的方式,写异步的代码
AF.request("https://httpbin.org/get").response { response in
    debugPrint(response)
}

Alamofire 默认采样内联闭包的方式,处理异步的网络请求

RxSwift 的语法,更加优雅

func getSearchResults(_ query: String) -> Observable<[WikipediaSearchResult]>

  • 响应式编程, 可以做数据绑定

observable 把数据和事件,包裹在一起。

建立响应,需要数据和事件, 数据是流动的,是最新状态的数据

事件,就是通知来事情了,告诉订阅者有消息

进入套路写法

1,要处理异步任务的函数,

//  input -> output
//  Value -> Observable<T>

func reachedBottom(offset: CGFloat = 500.0) -> ControlEvent<Bool>

函数,有输入,有输出,输出是异步的,

异步编程,事件尚未发生,先把流程整理清楚

处理异步,不是通过添加参数,熟悉的内联闭包

2,要处理异步任务的类,一般就是 ViewModel

ViewModel 也是有输入,有输出

Swift 的类,有协议支持,可以针对输入、输出作出行为规范

与上面的函数,继续区别,

类可以有状态,可以有几个属性,记录下当前的状态,

函数是无状态的,没有属性来记录


例子是 Papr 的首页

2.1 ,找出所有的输入,给 viewModel

上图中有 3 个,下拉刷新 ( 顶上的刷新控件 ),上拉更多,右上角按钮切换最新/最热

class HomeViewController{

    func bindViewModel() {
        let inputs = viewModel.inputs
        let rightBarButtonItemTap = rightBarButtonItem.rx.tap.share()


        // 连输入

        rightBarButtonItemTap
            .scan(into: OrderBy.latest) { result, _ in
                result = (result == .latest) ? .popular : .latest
            }
            .bind(to: inputs.orderByProperty)
            .disposed(by: disposeBag)

        rightBarButtonItemTap
            .merge(with: refreshControl.rx.controlEvent(.valueChanged).asObservable())
            .map(to: true)
            .bind(to: inputs.refreshProperty)
            .disposed(by: disposeBag)

        collectionView.rx.reachedBottom()
            .bind(to: inputs.loadMoreProperty)
            .disposed(by: disposeBag)
           // ...
        }
}

将三种原始输入关联到,关心的三种事件

protocol HomeViewModelInput {

    // 刷新第一页
    var refreshProperty: BehaviorSubject<Bool> { get }

    // 刷新更多页
    var loadMoreProperty: BehaviorSubject<Bool> { get }

    // 更改排序逻辑,最新,还是最热
    var orderByProperty: BehaviorSubject<OrderBy> { get }
}

protocol HomeViewModelType {
    var inputs: HomeViewModelInput { get }
    var outputs: HomeViewModelOutput { get }
}

final class HomeViewModel: HomeViewModelType, HomeViewModelInput, HomeViewModelOutput {

    var inputs: HomeViewModelInput { return self }
    var outputs: HomeViewModelOutput { return self }


    let refreshProperty = BehaviorSubject<Bool>(value: true)
    let loadMoreProperty = BehaviorSubject<Bool>(value: false)
    let orderByProperty = BehaviorSubject<OrderBy>(value: .latest)

协议有点绕,有输入协议,有输出协议,有输入输出协议。

好处是,代码语意明确

viewModel.inputs , 就是 viewModel,

安全点

viewModel.inputs , 只能访问 viewModel 输入的相关属性


3. BehaviorSubject 的使用

Subject 既是事件流 Observable, 能够接受订阅,

也是事件的观察者 Observer ,可以产生事件

观察者对 BehaviorSubject 进行订阅时,BehaviorSubject 会将源 Observable 中最新的元素发送出来(如果不存在最新的元素,就发默认元素 ) , 接着发送随后产生的元素

这个例子的场景下,就是用户刚进来,用户没点击,没产生事件流 observable,

也要有内容看啊,这里使用了 BehaviorSubject 发送默认的特性。

简单理解, viewDidLoad 里面有个网络请求界面初始化,简化为 BehaviorSubject

  • 不需要初始状态,可采用 PublishSubject,PublishSubject 只发送事件给当前的订阅者

来一个事件,处理一个

4, 继续说 ViewModel

上图中,用户的行为,构成了 ViewModel 的输入,

要展示给用户的状态,构成了 ViewModel 的输出

上图中,要管理的输出状态,有五个,

  • 点击右上角按钮,切换最新/最热的图片
  • 是否在请求第一页,控制刷新控件的旋转
  • 请求第一页的时候,在刷新。和底部上拉刷新更多的时候,右上角按钮,不可点击
  • 请求第一页的时候,刷新控件旋转。请求第一页完成,刷新控件隐藏。
  • 网络请求到的数据,列表展示出来
class HomeViewController{

        func bindViewModel() {
                let outputs = viewModel.outputs
                // ...
                // 用输出


        outputs.isOrderBy
            .map { $0 == .popular ? Papr.Appearance.Icon.flame : Papr.Appearance.Icon.arrowUpRight }
            .bind(to: rightBarButtonItem.rx.image)
            .disposed(by: disposeBag)

        outputs.isFirstPageRequested
            .negate()
            .bind(to: inputs.refreshProperty)
            .disposed(by: disposeBag)

        outputs.isRefreshing
            .merge(with: outputs.isLoadingMore)
            .negate()
            .bind(to: rightBarButtonItem.rx.isEnabled)
            .disposed(by: disposeBag)

        outputs.isRefreshing
            .bind(to: refreshControl.rx.isRefreshing(in: collectionView))
            .disposed(by: disposeBag)

        outputs.homeViewCellModelTypes
            .map {
                [HomeSectionModel(model: "", items: $0)]
            }
            .bind(to: collectionView.rx.items(dataSource: dataSource))
            .disposed(by: disposeBag)

        }
}

MVVM 不难。找出输入,转为 Observable, 交给 ViewModel .

要管理的状态,就是 ViewModel 输出的 observable,Subscribe 掉。

bind ,即把 observable , 交给一个可以 Subscribe 的方法,同样消耗掉, 给 fire 了

ViewModel 只是一个容器,集中化管理,数据流非常的清晰。

他做的事情,就是把输入变成输出,通过各种 operator

RxSwift 提供了强大的基础设施,各种好用的 operator

ViewModel 里面的代码同样使用声明式编程,很好读,没什么废话。

大理石图,很清晰​zhuanlan.zhihu.com/%5Bhttps://rxmarbles.com/#combineLatest%5D(https://rxmarbles.com/#combineLatest)

这里就省去了

5, RxSwift 可以省去中间状态,Less stateful

上图右上角的按钮,来回切,选择最新/ 最热

常规的 OOP , 会有一个记录属性,记录上一步的状态

这里采用了 operator 扫描 scan

scan 跟 operator reduce 类似,

scan 多了一个中间状态,可以把上一步的状态,带过来。正适合这里的场景。

Observable.of(10, 100, 1000)
            .scan(1) { lastValue, newValue in
                lastValue + newValue
            }
            .subscribe(onNext: { print($0) })
            .disposed(by: bag)

一般 scan 这么调用,提供一个初始值,闭包中拿上一步的值和新的值,来计算

11
111
1111

这里的

class HomeViewController{
         func bindViewModel() {
                rightBarButtonItemTap
                   .scan(into: OrderBy.latest) { result, _ in
                              result = (result == .latest) ? .popular : .latest
                   }
                   .bind(to: inputs.orderByProperty)
                 .disposed(by: disposeBag)

       }
}

这里 scan 的初始值,与 ViewModel 的初始值,保持一致。

都是最新 OrderBy.latest

final class HomeViewModel{
        let orderByProperty = BehaviorSubject<OrderBy>(value: .latest)
}

scan 里面的闭包,就是简单的采用了上一个状态,做了一个 toggle,

没有采用新值计算

5.1,数据是流动的,一般处理最新状态的数据,不是处理静态的数据

专心于数据的变化,不是很关心事件的来源

可观察好处: 省很多事...

如果不可观察,就需要很多胶水代码,保持同步。

模型变了,同步 UI

UI 改了,同步模型

双向绑定,就是可观察,

A 变,需要 B C D 跟着变。

用 observable ,自动处理。

使用 OOP, 手动处理。改完 A ,还要手动改 B C D .

  • 如果需要知道事件的来源,可以采用 Enum ,

Swift 的 Enum, 可以有关联值, Associated Values ,这样统一类型来源和 Value,

  • 更复杂的状态追踪,可以用结构体

5.2, ViewModel 也可以做控制器的路由跳转

代码见 jdisho/Papr

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值