在iOS开发中,MVC架构模式下,控制器会过于臃肿,所以目前比较流行的是MVVM架构模式。下面简单介绍一下iOS中MVVM的实践落地。
一、iOS的MVVM
下图是MVVM-C
设计模式的结构图,其中的C
指的不是控制器,而是作为展示或者关闭控制器的Coordinate
(协调器)。在实际开发中,我们一般在Controller
中完成展示或者关闭控制器
的任务,所以这里我们不关注协调器。
1. 职责划分
相比MVC
来说,新增了一个VM
, 下面是各个模块的职责:
VM:VM
是V
和M
之间的桥梁, 提供一系列属性用于View
的显示,属性包含将Model
变形转换为View
展示时应有的值。在iOS中通常还会负责网络请求及Model
更新。
VC:负责建立VM
中属性与View
的绑定关系;负责交互事件响应的具体逻辑;如果不建立图中协调器时,通常还包括还包括页面的跳转逻辑。
V: 视图的具体创建和用户交互监听,模型中数据的呈现逻辑。
M:负责存储和管理应用程序所需的数据,以及执行相关的业务逻辑。它不应该与V
或者VM
或者控制器
产生耦合。
2. 响应式编程RxSwift
来做绑定
- 上述提到
VC
中负责建立绑定关系,我们可以使用KVO
来实现,但在有需要数据变形转换时比较麻烦,不推荐;RxSwift
是专门用于响应式编程的一套框架,里面提供了很多变形相关的函数,能帮助更好的建立绑定关系。 - 使用
RxSwift
,可以根据需要来实现单向或者双向的绑定,当我们熟练RxSwift
的函数后,能提升我们的代码质量和便捷性。
二、一个响应式编程例子
下面是使用RxSwift实现的简单绑定:
var modelObject: ModelObject!
var disposeBag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
modelObject.valueObservable.map { possibleValue -> String in
if let value = possibleValue {
return "Selected value is: \(value)"
} else {
return "No value selected"
}
}.bind(to: self.textLabel.rx.text).disposed(by: disposeBag)
}
1. 为什么绑定很重要?
如上代码,相比于在很多地方设置 textLabel.text
的值,建立绑定后这个 textLabel
只会在最后被引用一次。响应式编程让我们从目的地---也就是数据的订阅者开始,一路通过数据变形进行回溯,直到到达原始的数据依赖 - 可观察量 (observable
)。通过这么做,数据管道的可观察量
,数据变形
以及订阅者
三者得以分离。
数据变形的部分是响应式编程所能带来的最大优势,但同时它也是学习曲线最为陡峭的部分。
2. RxSwift中的一些基本类型
Observable
是一个可观察量,我们可以对它们进行变形,订阅,或者将它们绑定到UI 元素上去。PublishSubject
是Observable
的一种,我们可以将值通过它来发送,这些值会被发给观察者,最终订阅者收到回调。BehaviorSubject
、ReplaySubject
和PublishSubject
类似,不过我们可以在没有任何观察者连接上它时就进行值的发送,有新的观察者时,会接收到之前发送过的暂存在 “重放”缓冲区上的值。Disposable
和DisposeBag
分别用来控制一个或多个订阅的生命周期。当一个Disposable
被销毁或者手动丢弃时,订阅行为就将结束,另外该订阅的所有的可观察量组成部分也将被释放。
3. RxSwift 中的部分变形函数
Map
:映射Filter
:过滤concat
:将两个A、B两个Observable“串行”起来,在A发送onComplete
前只会接受A的消息,A发送onComplete后才会接收B的消息。Merge
: 合并多个可观察序列,当其中一个发出消息时,会收到订阅回调。take
与take(while
、take(until
等:控制订阅次数或根据触发条件订阅。flatMapLatest
:只保留flatMapLatest
返回的最新一个Observable
的订阅(flatMapLatest
函数返回的是一个Observable
)。
三、一个双向绑定的例子
大多情况下,我们只需要单向绑定;但有时候可能会需要双向绑定,下面是一个双向绑定的示例: 登录页输入框要与VM的数据双向绑定。
class ViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var label: UILabel!
var userVM = UserViewModel()
let disposeBag = DisposeBag()
override func viewDidLoad() {
//将用户名与textField做双向绑定
userVM.username.asObservable().bind(to: textField.rx.text).disposed(by: disposeBag)
textField.rx.text.orEmpty.bind(to: userVM.username).disposed(by: disposeBag)
//将用户信息绑定到label上
userVM.userinfo.bind(to: label.rx.text).disposed(by: disposeBag)
}
}
// 我们可以将双向绑定定义一个为一个操作符(官方demo中有这个文件,可拷贝)
// 上述中双向绑定的代码可以简化为:
//将用户名与textField做双向绑定
_ = self.textField.rx.textInput <-> self.userVM.username
如果感兴趣的话,可以在RxSwift
中的github
上的样板工程:
github.com/ReactiveX/R…