MVVM架构设计在iOS中的实践

在iOS开发中,MVC架构模式下,控制器会过于臃肿,所以目前比较流行的是MVVM架构模式。下面简单介绍一下iOS中MVVM的实践落地。

一、iOS的MVVM

下图是MVVM-C设计模式的结构图,其中的C指的不是控制器,而是作为展示或者关闭控制器的Coordinate(协调器)。在实际开发中,我们一般在Controller中完成展示或者关闭控制器的任务,所以这里我们不关注协调器。 MVVM-C.png

1. 职责划分

相比MVC来说,新增了一个VM, 下面是各个模块的职责:

VMVMVM之间的桥梁, 提供一系列属性用于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 的一种,我们可以将值通过它来发送,这些值会被发给观察者,最终订阅者收到回调。
  • BehaviorSubjectReplaySubject PublishSubject 类似,不过我们可以在没有任何观察者连接上它时就进行值的发送,有新的观察者时,会接收到之前发送过的暂存在 “重放”缓冲区上的值。
  • Disposable DisposeBag 分别用来控制一个或多个订阅的生命周期。当一个Disposable 被销毁或者手动丢弃时,订阅行为就将结束,另外该订阅的所有的可观察量组成部分也将被释放。

3. RxSwift 中的部分变形函数

  • Map:映射
  • Filter:过滤
  • concat:将两个A、B两个Observable“串行”起来,在A发送onComplete前只会接受A的消息,A发送onComplete后才会接收B的消息。
  • Merge: 合并多个可观察序列,当其中一个发出消息时,会收到订阅回调。
  • taketake(whiletake(until等:控制订阅次数或根据触发条件订阅。
  • flatMapLatest:只保留flatMapLatest返回的最新一个Observable的订阅(flatMapLatest函数返回的是一个Observable)。

更具体的内容可以在RxSwift的网站查看

三、一个双向绑定的例子

大多情况下,我们只需要单向绑定;但有时候可能会需要双向绑定,下面是一个双向绑定的示例: 登录页输入框要与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…

猜你喜欢

转载自juejin.im/post/7247566462088101948