目录html
简要归纳一下 App 作的事?
架构是干什么的?git
一个项目,最重要的两个角色:Model 和 View。github
Model 决定是什么(数据),View 决定如何展现,其余的内容,基本都是处理二者的交互关系,以及提供这二者的服务。
若是要将 MV(X),其实基本讨论都逃不过二者间的交互:算法
反馈回路:编程
基于上图呢,咱们的大部分的工做其实能够拆分到下面 5 项中的一项:swift
对于上面5个问题的回答,构成了 App 设计模式的基础要件设计模式
view state 想一想页面跳转的逻辑,按钮是否可点击的状态网络
MVC 其实在上面的反馈回路中间插入了 Controller,使得每条线都通过了 Controller。多线程
单向数据流:好比更改了用户姓名,其实应该是只负责更新 model 也就是 name 字段,而后经过 kvo,让响应的nameLabel 改变显示闭包
最简单的模式,适用于绝大部分状况。
两个地方不太尽人意:
举个例子:这段代码有什么潜在的问题?
func changeName(name : String) { person.name = name namelabel.text = name }
责任重大,因此代码量很是容易就动辄几千行
对于这两个问题,其实也有不少的解决办法,第一个好比单向数据流,严格执行观察者模式;第二个办法很是很是多,好比 catogory,代理,代码拆分。(objc中国、唐巧)
为何还要学习下其余的架构?
Model - View - ViewModel + 协调器(Coordinator)
用过 MVVM的话 以为这张图有什么问题吗?
注意几点:
学习一下代码,看是怎么跑起来的
返回回路,已经基本被摘出去了
仍是那个 model
tableView.rx.modelDeleted(Item.self) .subscribe(onNext: { [unowned self] in self.viewModel.deleteItem($0) }).disposed(by: disposeBag)
func deleteItem(_ item: Item) { folder.value.remove(item) }
NotificationCenter.default.post(name: Store.changedNotification, object: notifying, userInfo: userInfo)
var folderContents: Observable<[AnimatableSectionModel<Int, Item>]> { return folderUntilDeleted.map { folder in guard let f = folder else { return [AnimatableSectionModel(model: 0, items: [])] } return [AnimatableSectionModel(model: 0, items: f.contents)] } }
注意,和通知序列相关的好多序列都会收到更新,从而更新各类 View 的显示或者状态。
viewModel.folderContents.bind(to: tableView.rx.items(dataSource: dataSource)).disposed(by: disposeBag)
先来看下函数响应式编程
一个拥有用户名和密码输入框的登陆界面:
产品经理说了需求:4句话:
通常的思路:
监听 Username 输入框,根据字符个数要考虑下面3件事:
监听 Password 输入框,根据字符个数考虑下面2件事:
因此开发过程:
有什么问题?
其实这个翻译过程,咱们本身把一些有联系的因素给放到一块儿处理了,好比按钮是均可点击,须要同时监听两个文本框的状态。
咱们可否只罗列条件,而后把这些条件扔给一个条件处理的机制,这个机制就能帮咱们正确的处理这些关系?
函数响应式编程来了:
咱们作两件事情:
开发过程变成了:
来看看代码:
let usernameValid = usernameOutlet.rx.text.orEmpty .map { $0.characters.count >= minimalUsernameLength } .share(replay: 1) // without this map would be executed once for each binding, rx is stateless by default let passwordValid = passwordOutlet.rx.text.orEmpty .map { $0.characters.count >= minimalPasswordLength } .share(replay: 1) let everythingValid = Observable.combineLatest(usernameValid, passwordValid) { $0 && $1 } .share(replay: 1) usernameValid .bind(to: passwordOutlet.rx.isEnabled) .disposed(by: disposeBag) usernameValid .bind(to: usernameValidOutlet.rx.isHidden) .disposed(by: disposeBag) passwordValid .bind(to: passwordValidOutlet.rx.isHidden) .disposed(by: disposeBag) everythingValid .bind(to: doSomethingOutlet.rx.isEnabled) .disposed(by: disposeBag) doSomethingOutlet.rx.tap .subscribe(onNext: { [weak self] _ in self?.showAlert() }) .disposed(by: disposeBag)
无需翻译,只须要罗列条件,接下来就是见证奇迹的时刻
直观的来看代码清晰不少,而后不怎么用动脑子,咱们下面来看看什么是函数响应式编程再来讲明他有哪些优缺点。
函数式编程是种编程范式
,它须要咱们将函数做为参数传递,或者做为返回值返还。咱们能够经过组合不一样的函数来获得想要的结果。
经过函数这个“管道”,数据从一头通过“管道”到另外一头,就获得了想要的数据。
编程范式?(命令式、声明式、函数式)http://www.javashuo.com/article/p-driicrqp-gb.html
函数式编程 + 响应
经过函数构建数据序列,最后经过适当的方式来响应这个序列,就是函数响应式编程。
在 Swift 中,咱们是用 RxSwift 来实现函数响应式编程!
核心角色有如下5个
以下图所示:
一个序列,随着时间的流逝,这个队列将陆续产生一些能够被观察的值。
你能够将温度看做是一个序列,而后监测这个序列产生的值,最后对这个值作出响应。例如:当室温高于 33 度时,打开空调降温。
函数响应式编程里最重要的就是构造序列。在函数响应式编程中,一切均可以看做是序列。
其实对于值的变化的队列,好理解,那么一次操做,或者一次网络请求任务也看作是队列,这个怎么实现的?
大多数序列能够产生3种事件:
public enum Event<Element> { case next(Element) case error(Swift.Error) case completed }
因此当你想任何东西封装成一个序列,只要 create 一个序列,而后在原有逻辑基础上在适当的时机调用这些事件便可,而且 RxSwift 已经帮咱们建立了大量的序列:
button 的点击
textField 的当前文本
switch 的开关状态
Notification 队列
若是本身建立,就须要调用上面说的事件了,好比咱们手动建立一个序列:
let numbers: Observable<Int> = Observable.create { observer -> Disposable in observer.onNext(0) observer.onNext(1) observer.onNext(2) observer.onNext(3) observer.onNext(4) observer.onNext(5) observer.onNext(6) observer.onNext(7) observer.onNext(8) observer.onNext(9) observer.onCompleted() // 结束 return Disposables.create() }
除了普通的队列,框架还提供了好多其余的队列提供了不一样的特性。
一个序列,其实就是一个被观察者
观察者是:观察序列的,响应序列事件的角色
tap.subscribe(onNext: { [weak self] in self?.showAlert() }, onError: { error in print("发生错误: \(error.localizedDescription)") }, onCompleted: { print("任务完成") })
建立观察者最直接的方法就是在 Observable 的 subscribe 方法后面描述,事件发生时,须要如何作出响应。而观察者就是由后面的 onNext,onError,onCompleted的这些闭包构建出来的。
一样框架为咱们提供了不少观察者,几乎每一个类的全部属性(包括自定义类),class.rx.xx 均可以做为观察者。
viewModel.navigationTitle.bind(to: rx.title).disposed(by: disposeBag) viewModel.noRecording.bind(to: activeItemElements.rx.isHidden).disposed(by: disposeBag) viewModel.hasRecording.bind(to: noRecordingLabel.rx.isHidden).disposed(by: disposeBag) viewModel.timeLabelText.bind(to: progressLabel.rx.text).disposed(by: disposeBag) viewModel.durationLabelText.bind(to: durationLabel.rx.text).disposed(by: disposeBag) viewModel.sliderDuration.bind(to: progressSlider.rx.maximumValue).disposed(by: disposeBag) viewModel.sliderProgress.bind(to: progressSlider.rx.value).disposed(by: disposeBag) viewModel.playButtonTitle.bind(to: playButton.rx.title(for: .normal)).disposed(by: disposeBag) viewModel.nameText.bind(to: nameTextField.rx.text).disposed(by: disposeBag)
观察者有两种:咱们此次只讲下 Binder
Binder 主要有如下两个特征:
因此不少UI 观察者都使用 Binder 去实现,只处理 Next ,而且在 主线程响应。
因为页面是否隐藏是一个经常使用的观察者,因此应该让全部的 UIView 都提供这种观察者:
extension Reactive where Base: UIView { public var isHidden: Binder<Bool> { return Binder(self.base) { view, hidden in view.isHidden = hidden } } }
usernameValid .bind(to: usernameValidOutlet.rx.isHidden) .disposed(by: disposeBag)
这样你没必要为每一个 UI 控件单首创建该观察者。这就是 usernameValidOutlet.rx.isHidden 的由来,许多 UI 观察者 都是这样建立的。
有了序列(被观察者 Observable)和观察者 (Observer),还差一点什么?
我有了一个时间戳的序列,怎么和观察者(birthdayLabel.rx.text)绑定?
我有了 Username 和 Password 是否有效的序列,怎么和 Button 是否能够点击的观察者(doSomethingOutlet.rx.isEnabled)绑定?
序列须要变形、合并、相互影响!
操做符能够帮助你们建立新的序列,或者变化组合原有的序列,从而生成一个新的序列。
https://beeth0ven.github.io/RxSwift-Chinese-Documentation/content/decision_tree.html
这里只介绍一种最简单经常使用的操做符,map,知道操做符的含义便可。
let usernameValid = usernameOutlet.rx.text.orEmpty .map { $0.characters.count >= minimalUsernameLength } .share(replay: 1)
既然之后绑定和订阅,确定有取消绑定和订阅,怎么取消呢,就用到了 Disposable。
最经常使用的是清除包(DisposeBag) 或者 takeUntil 操做符 来管理订阅的生命周期。
var disposeBag = DisposeBag() // 来自父类 ViewController override func viewDidLoad() { super.viewDidLoad() ... usernameValid .bind(to: passwordOutlet.rx.isEnabled) .disposed(by: disposeBag) }
这个例子中 disposeBag 和 ViewController 具备相同的生命周期。当退出页面时, ViewController 就被释放,disposeBag 也跟着被释放了,那么这里的绑定(订阅)也就被取消了。这正是咱们所须要的。
Schedulers 是 Rx 实现多线程的核心模块,它主要用于控制任务在哪一个线程或队列运行。
let rxData: Observable<Data> = ... rxData // 序列的构建函数在后台运行 .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .userInitiated)) // 主线程监听和处理结果 .observeOn(MainScheduler.instance) .subscribe(onNext: { [weak self] data in self?.data = data }) .disposed(by: disposeBag)
我的感悟:
序列、操做符、观察者分别被封装成,交互过程当中的3个对象,将这3者进行了解耦。能够灵活的组合,对序列进行变形、合并生成新的可直接使用的序列。提供一种更为高效的编程方法,从数据变形的角度看是一种降维,因此这方面的 Bug 会少一些。同时,学习曲线过于陡峭,用很差极可能写出很反人类的代码,让别人根本无法读,另外使用绑定机制,出现一些 Bug 确实不易调试。
可是最难得的是这种思想,咱们还能够把函数看作是对象,能够做为参数传递,能够做为返回值,就像一些设计模式,将算法封装成对象,有了策略模式;将命令封装成对象,有了命令模式;将状态封装成对象,有了状态模式,都解决了一些领域里的问题。学习这种思想,让咱们之后在编码上可以已更加宽广的视角去看待问题。
init(initialFolder: Folder = Store.shared.rootFolder) { folder = Variable(initialFolder) folderUntilDeleted = folder.asObservable() // Every time the folder changes .flatMapLatest { currentFolder in // Start by emitting the initial value Observable.just(currentFolder) // Re-emit the folder every time a non-delete change occurs .concat(currentFolder.changeObservable.map { _ in currentFolder }) // Stop when a delete occurs .takeUntil(currentFolder.deletedObservable) // After a delete, set the current folder back to `nil` .concat(Observable.just(nil)) }.share(replay: 1) }
对一个属性生成一个序列的方式
“在数据源每次发出一个值的时候,它使用该值构建,开始,或者选择一个新的可观察量。不过这个变形可让>咱们基于第一个可观察量发出的状态,来订阅第二个可观察量。”
—— 摘录来自: Chris Eidhof. “App 架构。” iBooks.
让两个或者多个 Observables 按顺序串联起来
concat 操做符将多个 Observables
按顺序串联起来,当前一个 Observable
元素发送完毕后,后一个 `Observable
才能够开始发出元素。
concat 将等待前一个 Observable 产生完成事件后,才对后一个 Observable 进行订阅。若是后一个是“热” Observable ,在它前一个 Observable 产生完成事件前,所产生的元素将不会被发送出来。
var changeObservable: Observable<()> { return NotificationCenter.default.rx.notification(Store.changedNotification).filter { [weak self] (note) -> Bool in guard let s = self else { return false } if let item = note.object as? Item, item == s, !(note.userInfo?[Item.changeReasonKey] as? String == Item.removed) { return true } else if let userInfo = note.userInfo, userInfo[Item.parentFolderKey] as? Folder == s { return true } return false }.map { _ in () } }
每次收到通知,只有通过 filter 函数检验为 true 的元素,才会被放到序列中做为新事件。
同 currentFolder.changeObservable,这不过这个是和删除有关的通知,才会放到序列。
nil 将会在 takeUtil 执行后发出,想一想为啥?
屡次绑定只执行一次操做序列
咱们将 folder 这个可观察量,与其余由 model 驱动的,可能影响咱们 view 的逻辑的可观察量,进行了合并。获得的结果是一个新的可观察量 folderUntilDeleted,它会在底层文件夹对象发生变化时正确更新,而且在底层文件夹对象被从 store 中删除时将本身设置为 nil。
即便咱们不使用 MVVM 他的一些思想咱们仍是能够借鉴的。
RxSwift 中文文档:https://beeth0ven.github.io/RxSwift-Chinese-Documentation/
《App 架构》
Interactive diagrams of Rx Observables : http://rxmarbles.com
菜鸟教程 swift 教程: https://www.runoob.com/swift/swift-tutorial.html