学习RxCocoa& MVVM-C(二)

RxCocoa

RxCocoa已经将UIKit中很多视图控件的很多属性值封装成了 Observable
RxSwift是基础,它工作于各种类型的Swift,但是并不能指定用户交互、网络请求,但是RxCocoa就可以帮助我们做这些事情。RxCocoa是一个独立的库,允许我们使用许多预置的特性,这样能够更好的与UIKit和Cocoa进行整合。RxCocoa能够让我们进行响应式网络,响应式的用户交互和绑定数据模型到UI控件。大多数的UIKit控件都有响应式扩展,都是通过rx熟悉进行使用。

RxCocoa能够工作在多平台,iOS (iPhone, iPad, Apple Watch), Apple TV ,macOS。每个平台都有一系列自定义的封装,提供了许多UI控件的扩展和一些SDK类。比如:NSButton+Rx.swift

cancelButton.rx.tap
    .bind(to: viewModel.cancel)
    .disposed(by: disposeBag)

比如:按钮的点击,button.rx.tap 是 ControlEvent 类型,ControlEvent 实现了 ControlEventType 协议,而 ControlEventType 协议继承自 ObservableType,因此可以把 ControlEvent 理解为一种特殊的 Observable,它可以使用.asObservable()方法转换为 Observable。

UITextField+Rx.swift 中

public var text: ControlProperty<String?> {
        return value
    }
    
    /// Reactive wrapper for `text` property.
    public var value: ControlProperty<String?> {
        return base.rx.controlPropertyWithDefaultEvents(
            getter: { textField in
                textField.text
            },
            setter: { textField, value in
                // This check is important because setting text value always clears control state
                // including marked text selection which is imporant for proper input 
                // when IME input method is used.
                if textField.text != value {
                    textField.text = value
                }
            }
        )
    }

textfield.rx.text 属性, 是 ControlProperty 类型,ControlProperty 实现了 ControlPropertyType 协议,而 ControlPropertyType 协议继承自 ObservableType, ObserverType,因此可以把 ControlEvent 理解为一种特殊的 Subject,能够被订阅而且能够有新值的注入。

而 UILabel+Rx.swift

extension Reactive where Base: UILabel {
    
    /// Bindable sink for `text` property.
    public var text: Binder<String?> {
        return Binder(self.base) { label, text in
            label.text = text
        }
    }

    /// Bindable sink for `attributedText` property.
    public var attributedText: Binder<NSAttributedString?> {
        return Binder(self.base) { label, text in
            label.attributedText = text
        }
    }
    
}

label.rx.text 属性, 是 Binder 类型,而 Binder 实现了 ObserverType,因此可以把 Binder 理解为一种特殊的 Observer,它可以使用 .asObserver() 方法转换为 Observer。
可以看出 text 的实现不同,TextField 的值既可以监听,又可以被监听,而 label 的 text 的值只能监听。

在RxCocoa中绑定是单向的,只能由被观察者到观察者,不能反过来操作。执行绑定操作的基础就是函数bind(to:)。为了绑定一个观察者序列(observable)到其它实体(实体就是subject,能够处理值也可以写值。subject是非常的重要,在Cocoa中。因为像UILabel, UITextField, and UIImageView都是可变数据,能够被设置值和获取值),接受者(receiver)必须遵守观察者协议(ObserverType)。但是bind(to:)不仅仅是用于绑定用户界面和潜在的数据,也可以用于其它目的,例如:你能够使用bind(to:)创建一个独立的过程(processes),以致于观察者序列(observable)能够触发一个subject并且执行一些后台任务而并不需要显示任何内容在屏幕上。总而言之,bind(to:)就是特殊的 subscribe()版本。

常见控件用法

UITextField

textField.rx.text.orEmpty.changed.subscribe(onNext: {
    print("输出内容: \($0)")
}).disposed(by: disposeBag)

如果同时监听多个 textfield

Observable.combineLatest(inputField1.rx.text.orEmpty, inputField2.rx.text.orEmpty) {
    (textValue1, textValue2) in
        print("你输入的号码是: \(textValue1)-\(textValue2)")
}

UILabel

inputField.rx.text
    .bind(to: label.rx.text)
    .disposed(by: disposeBag)

UISlider

slider.rx.value.subscribe(onNext: {
    print("当前slider值为: \($0)")
}).disposed(by: disposeBag)

UISegmentedControl

let segmentControl = UISegmentedControl()
// ...
segmentControl.rx.selectedSegmentIndex.subscribe(onNext: {
    print("当前页: \($0)")
}).disposed(by: disposeBag)

UISwitch

switchButton.rx.isOn.bind(to: button.rx.isEnabled)
    .disposed(by: disposeBag)

UITableView

items.bind(to: tableView.rx.items) { tableView, row, element in
    let cell = tableView.dequeueReusableCell(withIdentifier: "cellid")!
    cell.textLabel?.text = "\(row): \(element)"
    return cell
}.disposed(by: disposeBag)

tableView.rx.itemSelected.subscribe(onNext: { indexPath in
    print("选中项的indexPath为: \(indexPath)")
}).disposed(by: disposeBag)

tableView.rx.modelSelected(String.self).subscribe(onNext: { item in
    print("选中项的标题为: \(item)")
}).disposed(by: disposeBag)

事件监听

通过 rx.controlEvent 可以监听控件的各种事件,且多个事件状态可以自由组合。很多控件都有 touch 事件,输入框还有一些独立事件。

inputTextField.rx.controlEvent([.editingDidBegin]).subscribe(onNext: {
        print("开始编辑")
    }).disposed(by: disposeBag)
        
        inputTextField.rx.controlEvent([.editingChanged]).subscribe(onNext: {
        print("正在编辑")
    }).disposed(by: disposeBag)

UITextView 还有一些特殊的事件

textView.rx.didBeginEditing.subscribe(onNext: {
    print("开始编辑")
}).disposed(by: disposeBag)

textView.rx.didEndEditing.subscribe(onNext: {
    print("结束编辑")
}).disposed(by: disposeBag)

textView.rx.didChange.subscribe(onNext: {
    print("内容发生改变")
}).disposed(by: disposeBag)

textView.rx.didChangeSelection.subscribe(onNext: {
    print("选中部分发生改变")
}).disposed(by: disposeBag)

详细的介绍可以参考:RxSwift笔记 - RxCocoa 基础

Driver

如果我们的序列满足如下特征,就可以使用它:

  • 不会产生 error 事件
  • 一定在主线程监听(MainScheduler)
  • 共享状态变化(shareReplayLatestWhileConnected)

2,为什么要使用 Driver?
(1)Driver 最常使用的场景应该就是需要用序列来驱动应用程序的情况了,比如:

  • 通过 CoreData 模型驱动 UI
  • 使用一个 UI 元素值(绑定)来驱动另一个 UI 元素值
    (2)与普通的操作系统驱动程序一样,如果出现序列错误,应用程序将停止响应用户输入。
    (3)在主线程上观察到这些元素也是极其重要的,因为 UI 元素和应用程序逻辑通常不是线程安全的。
    (4)此外,使用构建 Driver 的可观察的序列,它是共享状态变化。

RxSwift的使用详解18(特征序列2:Driver) 这篇文章里 Driver 讲的特别详细。但是 Driver 适合不会发送错误信号的,要处理错误还是要用 Observable。

MVVM-C

Model - View - ViewModel — Driven by Coordinators
先提一下 MVVM 即模型-视图-视图模型。【模型】指的是后端传递的数据。【视图】指的是所看到的页面。【视图模型】mvvm模式的核心,它是连接view和model的桥梁。

通过将业务逻辑移动到 ViewModel 中,我们将业务逻辑从 ViewController 中分离出来。然而,当我们的目标是可重用的、灵活的代码时,仍然存在导航问题。一旦在整个视图或视图模型中放置导航,就会创建很多耦合,从而使重用代码变得更加困难。为了解决这一问题,我们将 Coordinator 引入到 MVVM 体系结构中,从而使用了MVVM-C。

详细介绍一下 MVVM 的每部分的职责:

Coordinator

主要负责流程和导航。

Coordinator 主要职责:

  • 创建并展示新的 ViewController, 以及为该 ViewController 创建 ViewModel 并提供外部依赖项。
  • 需要导航时作为 ViewController 方法的委托。例如: didFinish(), showDetail(), openUrl(),…
  • 创建新的子 Coordinator 作为导航流程的一部分。
  • 使用网络或者本地存储等 Service 获取数据用于导航。最好应该注入 Service 以使其更易于测试。
  • 处理 URI 导航
  • 存储数据并在模块之间进行传输

Coordinator 不能做的:

  • 修改数据
  • 改变用户界面
  • 直接使用UI

ViewModel

提供数据并保持 View 或者 ViewController 状态,因此 ViewModel 根本不需要导入 UIKit 框架。

ViewModel 主要职责:

  • 从提供的 Service 中获取数据(通过依赖注入的方式获取,不能直接使用 Service,可以方便进行单元测试)
  • 存储 ViewController 的状态
  • 在 ViewModel 的 ViewController 中为子视图创建、存储和通信子 ViewController。
  • 决定应该向用户显示哪些数据(filter()、map()、分页)
  • 验证用户输入

ViewModel 不能做的:

  • 显示任何视图
  • 决定如何显示任何数据。
  • 直接使用 Service
  • 页面跳转

ViewController 和 View

主要展示视图和用户交互。

ViewController 主要职责:

  • 决定如何向用户显示数据
  • 通知 coordinator 事件流程

ViewController 不能做的:

  • 决定向用户展示什么数据
  • 跳转另一个 ViewController
  • 调用 Service

Service

主要提供数据

Service 的主要职责:

  • 访问网络,核心数据,user defaults
  • 将原始数据修改为可读的形式(解析JSON,将CoreData转换为简单对象,…)

服务不能做的:

  • 与其他服务通信
  • 过滤数据

优点

使用MVVM-C模式的好处如下:

  • 解耦: 职责的分离更加清晰,也就是说,当您想要更改与应用程序跳转相关的内容时,Coordinator 是惟一需要修改的组件。
  • 可测试性: 由于所有 Coordinator 调用都指向一个可替换的 Coordinator,因此 ViewModel 变得更加容易测试。
  • 更改导航: 每个 Coordinator 只负责一个组件,并且没有与其父组件有任何假设。因此,它可以放在我们想放的任何地方。
  • 代码重用: 除了可以更改导航,还可以重用组件。例如,可以从应用程序中的多个点调用相同的 Coordinator。

RxSwift & MVVM Demo:
https://github.com/wf96390/RxSwiftMVVMDemo

参考:
https://www.colabug.com/826982.html
https://blog.csdn.net/qq_32670879/article/details/85158785
https://www.jianshu.com/p/c30628d60803
https://blog.csdn.net/Mazy_ma/article/details/81913976
http://www.hangge.com/blog/cache/detail_1942.html
https://blog.csdn.net/longshihua/article/details/72801096
http://www.danielhall.io/the-problems-with-mvvm-on-ios

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值