iOS底层原理(七):系统架构之MVVM+RxSwift

前言
目前iOS领域最流行的架构,当属MVVM+RxSwift了,这种架构有很多优势,也有一些缺点,得根据自身的需求合理的选择
一、MVVM

MVVM 最早于 2005 年被微软的架构师 John Gossman 提出,并且应用在微软的软件开发中。在 iOS 开发中实践 MVVM 的话,通常会把大量原来放在 Controller 里的视图逻辑和数据逻辑移到 ViewModel里,从而有效的减轻了 Controller 的负担。

另外通过分离出来的ViewModel 获得了更好的测试性,我们可以针对 ViewModel 来测试,解决了界面元素难于测试的问题。MVVM 通常还会和一个强大的绑定机制一同工作,一旦 ViewModel 所对应的 Model 发生变化时,ViewModel 的属性也会发生变化,而相对应的 View 也随即产生变化。

MVVM的优点MVVM将逻辑交给了ViewModel层控制器只需要负责数据绑定,如此一来控制器的压力就减轻了很多,而且ViewModel与控制器及其页面相互独立,可以很方便给ViewModel写单元测试,适合业务逻辑比较复杂的大型项目

MVVM的缺点:MVVM学习成本比较高,需要对ViewModel和View进行双向绑定,当出现Bug时,会迅速被传递,比较难以调试

MVVM的组成:MVVM由Model、View、ViewModel、Controller四部分组成:

  • View是视图显示层,只展示UI,不做任何逻辑处理,与绑定ViewModel;

  • Model是数据模型,一般通过网络层进行一次封装;

  • ViewModel是视图模型,负责处理逻辑,包括网络请求、业务逻辑、UI逻辑等,可以把ViewModel想象成一个黑盒,传进去输入源后,经过ViewModel进行处理,得到输出源

  • Controller是胶水层,负责拼接UI,装配ViewModel,并且将ViewModel与View进行绑定

二、RxSwift

RxSwift 是 Rx 的 Swift 版本,ReactiveX(简写: Rx)是一个跨平台框架,它不仅可以用来写 iOS ,你还可以用它来写 AndroidWeb 前端后台。并且每个平台都和 RxSwift 一样有一套 Rx 生态系统。Rx 支持多种编程语言,如:Swift,Java,JS,C#,Scala,Kotlin,Go 等。只要你掌握了其中一门语言,你很容易就能够熟悉其他的语言。

推荐看RxSwift官方文档学习,以下是我学习官方文档的学习笔记:

    1. Observable-序列是用来描述元素异步产生的过程,万物皆可序列,类型有:Single、Completable、Maybe、Driver、Signal、ControlEvent

      1>. Single序列要么只能发出一个元素,要么产生一个error事件,不会共享附加作用

      2>. Driver序列不会产生error事件,一定在主线程回调,共享附加作用,所以UI序列一般都用Driver,但是Driver会对新观察者重新发送上一个元素

      3>. Signal序列Driver序列类似,也不会产生error事件,一定在主线程回调,共享附加作用,区别就是,Signal不会对新观察者回放上一个元素

    1. 一个序列如果发出了 error 或者 completed 事件,那么整个序列都会被终止,所有内部资源都会被释放。如果你需要提前释放这些资源或取消订阅的话,一般使用DisposeBag或者takeUntil
    1. 有的序列有共享附加作用的特征,有的序列不共享附加作用

      1>. 共享附加作用是指:如果一个序列共享附加作用,那在第二次订阅时,不会重新发起网络请求,而是共享第一次网络请求(附加作用),例如:Driver、Signal、ControlEvent

      2>. 不共享附加作用是指:如果一个序列不共享附加作用,那在第二次订阅时,会重新发起网络请求,而不是共享第一次网络请求(附加作用),例如:Single、Completable、Maybe

    1. Observer-观察者,是用来监听序列的,当序列产生元素时,需要对此事件作出响应,分为两种:AnyObserver、Binder

      1>. AnyObserver观察者可以描述任意一种观察者,例如:接受到网络请求序列的元素时,就可以用AnyObserver作为响应者

      2>. Binder观察者不会处理错误事件,并且确保响应都在给定的Scheduler上执行,默认是MainScheduler,拥有这些特性它天生就是一个UI观察者,所以对UI序列的观察首选就是Binder,例如:接收到用户名是否有效的序列的元素时,就可以用Binder作为响应者,如下所示:

//观察者,用来响应事件
let observer: Binder = Binder(usernameValidOutlet) { (view, isHidden) in
    view.isHidden = isHidden
}
//用户名是否有效的序列,一旦发出了元素,就用Binder来响应
usernameValid.bind(to: observer).disposed(by: disposeBag)

//RxCocoa帮我们给UIControl封装好了Binder响应者,也可以这么写:
usernameValid.bind(to: usernameValidOutlet.rx.isHidden).disposed(by: disposeBag)
    1. 有一种特殊的序列,既可以是序列,又可以是观察者,例如:textFieldtext属性,既可以看由用户输入而产生的序列,又可以看做显示内容的观察者,如下所示:
// 作为可监听序列
let observable = textField.rx.text
observable.subscribe(onNext: { text in show(text: text) })

// 作为观察者
newObservable().bind(to: textField.rx.text)
    1. 操作符可以帮助大家创建新的序列,也可以对原有的序列进行加工,使之成为一个新的序列,例如通过map操作符可以将输入的用户名,转换成用户名是否有效,从字符串序列转成了布尔值序列,这里列举几个常用的操作符:

      1>. catchErrorJustReturn操作符将一个error事件替换成其他元素,并且结束该序列,如下所示:

let fails = Observable()
fails.catchErrorJustReturn("555").subscribe{ print($0) }.disposed(by: disposeBag)

fails.onNext("1")
fails.onNext("2")
fails.onError(Error.test)

输出: 1、2、555、complete4d

2>. combineLatest操作符,当多个Observables中任何一个发出一个元素,如果其他Observables都已经存在元素的话,就会通过一个函数进行组合,然后发出一个元素

3>. map操作符,通过一个转换函数,将Observable里面的每个元素都转换一遍,转换函数的闭包返回的是具体的类型,如下所示:

let newObservable = oldObservable.map { (string) -> String  in
       return "前缀" + string + "后缀 "
}

4>. flatMap操作符,通过一个转换函数,将Observable序列里面的每个元素都转换成一个Observable序列,然后将这些序列合并,这个操作符可以将子序列的元素一次性全部发出来

        let oldObservable = Observable.create { observer -> Disposable in
            observer.onNext("oldNext1")
            observer.onNext("oldNext2")
            observer.onNext("oldNext3")
            observer.onCompleted()
            return Disposables.create()
        }
        
        let newObservable =  oldObservable.flatMap { (string) -> Observable in
            return Observable.create { observer -> Disposable in
                observer.onNext("\(string)------newNext1")
                observer.onNext("\(string)------newNext2")
                observer.onNext("\(string)------newNext3")
                observer.onCompleted()
                return Disposables.create()
            }
        }
        
        newObservable.subscribe(onNext: { (string) in
            print("\(string)")
        })
输出 :
oldNext1------newNext1
oldNext1------newNext2
oldNext1------newNext3
oldNext2------newNext1
oldNext2------newNext2
oldNext2------newNext3
oldNext3------newNext1
oldNext3------newNext2
oldNext3------newNext3

5>. withLatestFrom操作符将两个Observable最新元素通过一个函数组合,当第二个Observable有元素后,第一个Observable发出的元素就会跟第二个Observable的最新元素进行组合,如下所示:

        let firstObservable = PublishSubject()
        let secondObservable = PublishSubject()
        firstObservable.withLatestFrom(secondObservable) { (firstString, secondString) -> String in
            return firstString + secondString
        }.subscribe(onNext: { (string) in
            print(string)
        }).disposed(by: bag)
        
        //此时secondObservable没有元素,所以firstObservable发出的元素不会被订阅
        firstObservable.onNext("firstObservbale--111\n")
        secondObservable.onNext("secondObservable--111\n")
        
        //此时secondObservable已经有一个元素了,所以firstObservable发出的元素会与SecondObservbale的最新元素进行组合,然后被订阅
        firstObservable.onNext("firstObservbale--222\n")
        secondObservable.onNext("secondObservable--222\n")
        
        firstObservable.onNext("firstObservbalea--333\n")
        firstObservable.onNext("firstObservable--444\n")
输出:
firstObservbale--222
secondObservable--111

firstObservbalea--333
secondObservable--222

firstObservable--444
secondObservable--222
    1. Schedulers 是 Rx 实现多线程的核心模块,它主要用于控制任务在哪个线程或队列运行
    • 我们用 subscribeOn 来决定数据序列的构建函数在哪个 Scheduler 上运行;
    • 我们用 observeOn 来决定在哪个 Scheduler 监听这个数据序列;
    • 一个比较典型的例子就是,在后台发起网络请求,然后解析数据,最后在主线程刷新页面。你就可以先用 subscribeOn 切到后台去发送请求并解析数据,最后用 observeOn 切换到主线程更新页面,如下所示:
GCD实现:
// 后台取得数据,主线程处理结果
DispatchQueue.global(qos: .userInitiated).async {
    let data = try? Data(contentsOf: url)
    DispatchQueue.main.async {
        self.data = data
    }
}

RxSwift实现:
let rxData: Observable = ...

rxData
    .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated))
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { [weak self] data in
        self?.data = data
    })
    .disposed(by: disposeBag)
    1. 一旦序列里产出了一个error事件,整个序列将被终止,除非你进行了错误处理

    1>. catchError可以在错误产生时,用一组备用元素将错误替换掉,如下所示,当搜索出现错误时,用空数组来替换错误,以保证整个序列不会被终止

searchBar.rx.text.orEmpty
    ...
    .flatMapLatest { query -> Observable<[Repository]> in
        ...
        return searchGitHub(query)
            .catchErrorJustReturn([])
    }
    ...
    .bind(to: ...)
    .disposed(by: disposeBag)

2>. 将泛型设置成Result可以将错误传递下去,可以方便我们处理错误,如下所示:

        let second = GithubService.sharedValidationService.API.usernameAvailable("123456song123")
        let registerObservable = Observable.combineLatest(registeredButton.rx.tap, second).flatMap { (_,isValidate) -> Observable> in
            if isValidate{
                print("用户名有效")
                return GithubService.sharedValidationService.API.signup("123", password: "123456").map { (isSuccess) -> Result in
                    return isSuccess ? Result.success("注册成功") : Result.failure(SampleError())
                }.catchError { (error) -> Observable> in
                    Observable.just(Result.failure(error))
                }
            }else{
                print("用户名无效")
                return Observable.just(Result.failure(SampleError()))
            }
        }
        registerObservable.subscribe(onNext: { (result) in
            switch result{
            case .success(let string):
                print("注册成功,没有异常-\(string)")
            case .failure(let error):
                print("注册失败,出现异常了 = \(error)")
            }
        }, onError: { (error) in
            
        }).disposed(by: bag)
三、MVVM + RxSwift

MVVM+RxSwift的架构方式,根据输入源的不同,一般有两种:

两种方式的Github地址在这里

  1. 如果输入源能一次性拿到,那么可以用第一种方式,通过初始化输入源得到输出源,然后在Controller中将输出源与观察者进行绑定,如下所示:
protocol ViewModelType {
    associatedtype Input
    associatedtype Output
    
    var output: Output {get}
}

class SayHelloViewModel: ViewModelType{
    
    struct Input {
        let name: Observable
        let validate: Observable
    }
        
    struct Output {
        let greeting: Driver
    }
    
    let output: SayHelloViewModel.Output
    
    init(input: SayHelloViewModel.Input) {
        let greeting = input.name.map { (name) in
            if name.count < 5{
                return "请输入正确的姓名"
            }
            return "Hello \(name)"
        }.asDriver(onErrorJustReturn: "error")
        
        self.output = Output(greeting: greeting)
    }
}

class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!
    @IBOutlet weak var validationButton: UIButton!
    @IBOutlet weak var resultLabel: UILabel!
    
    var viewModel:  SayHelloViewModel?
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let inputs = SayHelloViewModel.Input(name: textField.rx.text.orEmpty.asObservable() , validate: validationButton.rx.tap.asObservable())
        viewModel = SayHelloViewModel(input: inputs)
        bindViewModel()
    }

    func bindViewModel(){
        viewModel?.output
            .greeting.drive(resultLabel.rx.text).disposed(by: disposeBag)
    }
}
  1. 如果输入源不能一次性拿到 ,需要分批拿到输入源,就需要用第二种方式,第二种方式会在更新输入源之后同步更新输出源,如下所示:
protocol ViewModelType2 {
    associatedtype Input
    associatedtype Output
    var input: Input { get }
    var output: Output { get }
}

class SayHelloViewModel2: ViewModelType2{
    let input: Input
    let output: Output
    
    struct Input {
        let name: AnyObserver
        let validate: AnyObserver
    }
    struct Output {
        let greeting: Driver
    }
    
    private let nameSubject = ReplaySubject.create(bufferSize: 1)
    private let validateSubject = PublishSubject()
    
    init() {
------先用两个输入源生成输出源,等后面在更新输入源,更新完成后输出源会自动更新
        let greeting = validateSubject.withLatestFrom(nameSubject).map { name in
            if name.count <= 5{
                return "用户名不能小于5"
            }
            return "Hello \(name)"
        }.asDriver(onErrorJustReturn: "error")
        //
        self.output = Output(greeting: greeting)
        self.input = Input(name: nameSubject.asObserver(), validate: validateSubject.asObserver())
    }
}
class TableViewController: UITableViewController {

    let CellIDs = ["FirstCell","SecondCell","ThirdCell"]
    let viewModel = SayHelloViewModel2()
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1;
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return CellIDs.count;
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell =  tableView.dequeueReusableCell(withIdentifier: CellIDs[indexPath.row], for: indexPath)
        (cell as? SayHelloViewModelBindable)?.bind(to: viewModel)
        return cell
    }
}
protocol SayHelloViewModelBindable {
    var bag: DisposeBag {get}
    func bind(to viewModel: SayHelloViewModel2)
}

class FirstTableViewCell: UITableViewCell, SayHelloViewModelBindable{

    @IBOutlet weak var textField: UITextField!
    let bag = DisposeBag()
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    
    ------更新ViewModel的输入源input.name
    func bind(to viewModel: SayHelloViewModel2){
        textField.rx.text.orEmpty.bind(to: viewModel.input.name).disposed(by: bag)
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

class SecondTableViewCell: UITableViewCell,SayHelloViewModelBindable {

    
    @IBOutlet weak var validationButton: UIButton!
    let bag = DisposeBag()
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    
    ------更新ViewModel的输入源input.validate 
    func bind(to viewModel: SayHelloViewModel2){
        validationButton.rx.tap.bind(to: viewModel.input.validate).disposed(by: bag)
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

class ThirdTableViewCell: UITableViewCell,SayHelloViewModelBindable {

    @IBOutlet weak var resultLabel: UILabel!
    let bag = DisposeBag()
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }
    
    ------将ViewModel的输出源output.greeting绑定到resultLabel上
    func bind(to viewModel: SayHelloViewModel2){
        viewModel.output.greeting.drive(resultLabel.rx.text).disposed(by: bag)
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

你可能感兴趣的:(iOS底层原理(七):系统架构之MVVM+RxSwift)