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
CSDN博客地址:
https://blog.csdn.net/wf96390/article/details/88370363
参考:
https://www.colabug.com/826982.html
https://blog.csdn.net/qq_32670879/article/details/85158785
//www.greatytc.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