学习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

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
RX Library 2.75 =============== The Set of Native Delphi Components for Borland Delphi versions 1, 2, 3, 4 & 5 and Borland C++ Builder 1, 3 & 4. 100% Source Code. Last revision date Oct 12, 1999. PLEASE FOLLOW THE INSTRUCTIONS PROVIDED IN THE INSTALLATION SECTION! TABLE OF CONTENTS ----------------- Latest Changes Overview History License Agreement Installation Demonstration Programs Source Files Using GIF Images Copyright Notes NEW FOR VERSION 2.75 -------------------- Delphi 5.0 & C++Builder 4.0 Compatibility New components: TRxLoginDialog New properties, events: TFormPlacement.RegistryRoot TFormPlacement.Version TFontComboBox.UseFonts TRxDBGrid.OnTopLeftChanged TRxDBLookupCombo.DisplayValues TStrHolder.Macros, TStrHolder.OnExpandMacros RxSpin.TValueType.vtHex New routines, methods, constants: SaveClipboardToStream, LoadClipboardFromStream (clipmon.pas) AppFileName, AppVerInfo (rxverinf.pas) XorString, XorEncode, XorDecode (strutils.pas) BUG FIXES. Overview -------- RX Library contains a large number of components, objects and routines for Borland Delphi with full source code. This library is compatible with Borland Delphi 1, 2, 3, 4, 5 and Borland C++ Builder 1, 3, 4. This collection includes over 60 native Delphi components. RX Library is a freeware product. Feel free to distribute the library as long as all files are unmodified and kept together. The authors disclaim all warranties as to this software, whether express or implied, including without limitation any implied warranties of merchantability or fitness for a particular purpose. Use under your own responsibility, but comments (even critique) in English (or in Russian) are welcome. 1. Components: TRxDBLookupCombo provides an incremental search through lookup list by directly typing into the combo control while the lookup list is displayed, LookupSource can refer to TTable, TQuery, TRxQuery or TQBEQuery. It even incrementally searches on the query results and much more... TRx
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值