RxSwift和RxCocoa入门

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"本文主要来自"},{"type":"link","attrs":{"href":"https://www.raywenderlich.com/1228891-getting-started-with-rxswift-and-rxcocoa","title":""},"content":[{"type":"text","text":"Getting Started With RxSwift and RxCocoa"}]},{"type":"text","text":"这篇文章"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"命令式编程 vs 响应式编程"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"目前大部分面向对象语言都是命令式的编程范式,即通过代码下达命令给系统;虽然可以通过各种技术手段知道数据的变化,但是不能做到自动化地通知。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"响应式编程则是想让数据的变化可以自动地通知,你不再需要关心特定状态,这个状态通知到响应的过程,响应式编程(库)已经帮你处理了"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"维基百科上有一个更明确的例子:"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对于命令式编程,当a = b + c, a由b+c计算得到;但是之后,b和c发生了变化,这个变化对a并不生效,a还是保持原有的值;而当使用响应式编程时,当b和c发生变化,程序不需要再次执行a = b + c公式,a的值就会自动更新。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"响应式编程库现状"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前分为两派"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ReactiveX主导的rx系列库,语言支持如RxSwift, RxKotlin, 平台支持如RxAndroid, RxCocoa"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ReactiveCocoa(GitHub发起)库,语言仅支持swift,objective-c,平台仅支持iOS"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"相同点:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"都是Reactive Functional Programming(响应式函数编程)的实现"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"二者github star数不分伯仲,均接近20k级别"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"都有很好的社区支持"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"不同点:"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Hot、Code Signal实现API不同:RAC设计了两个api分别对应hot、code signal,RxSwift仅有一个"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"错误处理,RAC相对容易一些"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"UI Bindings,RAC有不少历史包袱,RxSwift则更容易使用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Rx系列支持更多语言和平台"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"关于这两个库的详细对比,见:"},{"type":"link","attrs":{"href":"https://www.raywenderlich.com/1190-reactivecocoa-vs-rxswift","title":""},"content":[{"type":"text","text":"ReactiveCocoa vs RxSwift"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"RxSwift和RxCocoa"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RxSwift: 响应式编程在Swift语言领域的实现库"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RxCocoa: 针对Cocoa平台(iOS & Mac OS)的响应式编程库"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"Observables and Observers"}]},{"type":"bulletedlist","content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个"},{"type":"text","marks":[{"type":"italic"},{"type":"strong"}],"text":"Observable"},{"type":"text","text":"发送变化通知"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一个"},{"type":"text","marks":[{"type":"italic"},{"type":"strong"}],"text":"Observer"},{"type":"text","text":"订阅一个Observable,当Observable有变化时会被通知"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"多个Observer可以监听一个Observable,当发生变化时,所有Observer都会被通知"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"DisposeBag"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"DisposeBag是RxSwift提供的处理ARC和内存管理的工具。销毁一个父对象时,会使得DisposeBag中的Observer对象同时销毁。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"当持有DisposeBag的对象的"},{"type":"codeinline","content":[{"type":"text","text":"deinit()"}]},{"type":"text","text":"调用时,每个disposable Observer都会取消对监听对象的订阅,这时ARC就可以正常回收内存了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果没有DisposeBag的话,可能会出现两种情况,要么是observer会保留下来不被销毁,继续监听;要么被释放,造成崩溃。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"建立Observer对象时,记得同时添加到DisposeBag中,来让DisposeBag帮助你回收该对象。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"开始吧"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先下载"},{"type":"link","attrs":{"href":"https://koenig-media.raywenderlich.com/uploads/2019/04/Materials_rxswift5.zip","title":""},"content":[{"type":"text","text":"示例工程"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"打开编译后即可看到如下界面"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2a/2a092c225915e88d0e4c5e5d9a56fa96.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这个例子功能很简单:选择巧克力后,点击右上角可以结账,然后进行模拟支付。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"非reactive的实现"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"text","marks":[{"type":"strong"}],"text":"ChocolatesOfTheWorldViewController.swift"},{"type":"text","text":"中可以看到实现"},{"type":"codeinline","content":[{"type":"text","text":"UITableViewDelegate"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"UITableViewDataSource"}]},{"type":"text","text":"的extension。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在观察一下"},{"type":"codeinline","content":[{"type":"text","text":"updateCartButton()"}]},{"type":"text","text":"这个方法,它用来更新购物车中的巧克力数量,在下面两种情况时调用:"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"viewWillAppear(_:)"},{"type":"text","text":":在view controller显示之前"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"tableView(_:didSelectRowAt:)"},{"type":"text","text":":当点击列表,添加巧克力到购物车时"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这就是命令式编程方式实现:你必须手动调用方法来更新购物车巧克力数量。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"使用RxSwift改写购物车商品数量"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"购物车商品信息保存在"},{"type":"codeinline","content":[{"type":"text","text":"ShoppingCart.sharedCart"}]},{"type":"text","text":"这个单例中"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"定义在ShoppingCart.sharedCart中:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"var chocolates: [Chocolate] = []"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"虽然可以给其定义中添加一个"},{"type":"codeinline","content":[{"type":"text","text":"didSet"}]},{"type":"text","text":"闭包,但问题是,这种做法只能在整个数组更新时才能被通知,而不是数组中任意元素变化就可以得到通知。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"针对这种情况,RxSwift提供了解决方案,按照如下方式创建"},{"type":"codeinline","content":[{"type":"text","text":"chocolates"}]},{"type":"text","text":"变量:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"let chocolates: BehaviorRelay = BehaviorRelay(value: [])"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"使用RxSwift的"},{"type":"codeinline","content":[{"type":"text","text":"BehaviorRelay"}]},{"type":"text","text":"对象,持有一个Chocolate数组类型的值。这么做的目的是:通过"},{"type":"codeinline","content":[{"type":"text","text":"BehaviorRelay"}]},{"type":"text","text":"对象的"},{"type":"codeinline","content":[{"type":"text","text":"asObservable()"}]},{"type":"text","text":"可以得到一个observable,这样我们就可以添加监听者来订阅"},{"type":"codeinline","content":[{"type":"text","text":"BehaviorRelay"}]},{"type":"text","text":"对象的"},{"type":"codeinline","content":[{"type":"text","text":"value"}]},{"type":"text","text":"(chocolate数组)的变化。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述做法的缺点是,对chocolate数组的修改必须修改为使用"},{"type":"codeinline","content":[{"type":"text","text":"accept(_:)"}]},{"type":"text","text":",这是"},{"type":"codeinline","content":[{"type":"text","text":"BehaviorRelay"}]},{"type":"text","text":"为修改"},{"type":"codeinline","content":[{"type":"text","text":"value"}]},{"type":"text","text":"属性提供的方法。由于对数据的访问方式发生了变化,代码中相应的地方也要做修改。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"text","marks":[{"type":"strong"}],"text":"ShoppingCart.swift"},{"type":"text","text":"中"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"totalCost()"}]},{"type":"text","text":"方法的修改:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"return chocolates.reduce(0) {\n// 修改为\nreturn chocolates.value.reduce(0) {"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"itemCountString()"}]},{"type":"text","text":"方法的修改:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"guard chocolates.count > 0 else {\n// 修改为\nguard chocolates.value.count > 0 else {\n\nlet setOfChocolates = Set(chocolates)\n// 修改为\nlet setOfChocolates = Set(chocolates.value)\n\nlet count: Int = chocolates.reduce(0) {\n// 修改为\nlet count: Int = chocolates.value.reduce(0) {"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"text","marks":[{"type":"strong"}],"text":"CartViewController.swift"},{"type":"text","text":"中"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"reset()"}]},{"type":"text","text":"方法的修改:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"ShoppingCart.sharedCart.chocolates = []\n// 修改为\nShoppingCart.sharedCart.chocolates.accept([])"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"text","marks":[{"type":"strong"}],"text":"ChocolatesOfTheWorldViewController.swift"},{"type":"text","text":"中"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"updateCartButton()"}]},{"type":"text","text":"方法的修改:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"// 修改为\ncartButton.title = \"\\(ShoppingCart.sharedCart.chocolates.value.count) \\u{1f36b}\""}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"tableView(_:didSelectRowAt:)"}]},{"type":"text","text":"方法的修改:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"ShoppingCart.sharedCart.chocolates.append(chocolate)\n// 修改为\nlet newValue = ShoppingCart.sharedCart.chocolates.value + [chocolate]\nShoppingCart.sharedCart.chocolates.accept(newValue)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"完成上述修改后,我们就可以来对"},{"type":"codeinline","content":[{"type":"text","text":"chocolates"}]},{"type":"text","text":"添加observer了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在ChocolatesOfTheWorldViewController.swift中,新增:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"private let disposeBag = DisposeBag()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在"},{"type":"codeinline","content":[{"type":"text","text":"//MARK: Rx Setup"}]},{"type":"text","text":"的extension中添加:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"func setupCartObserver() {\n //1\n ShoppingCart.sharedCart.chocolates.asObservable()\n .subscribe(onNext: { //2\n [unowned self] chocolates in\n self.cartButton.title = \"\\(chocolates.count) \\u{1f36b}\"\n })\n .disposed(by: disposeBag) //3\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"上述代码即可实现对购物车的自动更新。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可以看到,RxSwift大量使用函数链,就是说每一个函数接收前一个函数的结果。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"对上述代码的解释:"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"从购物车中"},{"type":"codeinline","content":[{"type":"text","text":"chocolates"}]},{"type":"text","text":"得到一个"},{"type":"codeinline","content":[{"type":"text","text":"Observable"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"对"},{"type":"codeinline","content":[{"type":"text","text":"Observable"}]},{"type":"text","text":"调用subscribe(onNext:)来监听其值变化。"},{"type":"codeinline","content":[{"type":"text","text":"subscribe(onNext:)"}]},{"type":"text","text":"接收的闭包在每次值变化时都会被执行。闭包中的入参就是"},{"type":"codeinline","content":[{"type":"text","text":"Observable"}]},{"type":"text","text":"变化后的最新值。除非你取消订阅,或者dispose订阅,你会一直收到变化通知。这个方法的返回的一个"},{"type":"codeinline","content":[{"type":"text","text":"Disposable"}]},{"type":"text","text":"(也是一个"},{"type":"codeinline","content":[{"type":"text","text":"Observer"}]},{"type":"text","text":")。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"将上一步返回的"},{"type":"codeinline","content":[{"type":"text","text":"Observer"}]},{"type":"text","text":"加入到我们的定义"},{"type":"codeinline","content":[{"type":"text","text":"disposeBag"}]},{"type":"text","text":"中。这会让被订阅对象被销毁时,订阅者也被处理。"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"最后,删除"},{"type":"codeinline","content":[{"type":"text","text":"updateCartButton()"}]},{"type":"text","text":"方法的调用。"}]},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"然后执行代码,你会看到巧克力列表:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/10/10f2c45406ff73ede3f55e994e6dad47.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"但此时,点击单个巧克力,购物车位置一直显示的是“Item”。这是因为"},{"type":"codeinline","content":[{"type":"text","text":"setupCartObserver()"}]},{"type":"text","text":"没有被调用,导致"},{"type":"codeinline","content":[{"type":"text","text":"Observer"}]},{"type":"text","text":"并没有建立起来。在"},{"type":"text","marks":[{"type":"strong"}],"text":"ChocolatesOfTheWorldViewController.swift"},{"type":"text","text":"的"},{"type":"codeinline","content":[{"type":"text","text":"viewDidLoad()"}]},{"type":"text","text":"方法最后调用该它。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"再次编译运行,就会看到,当点击巧克力时,购物车自动更新了。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/56/5671880ee158663d3e7bcfca1d6b7622.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"使用RxCocoa对TableView进行Reactive改造"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RxCocoa为原生UI组件添加了响应式API。这本例中,使用TableView的响应式API,可以不再用自己实现"},{"type":"codeinline","content":[{"type":"text","text":"UITableViewDataSource"}]},{"type":"text","text":"和"},{"type":"codeinline","content":[{"type":"text","text":"UITableViewDelegate"}]},{"type":"text","text":"。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"实现步骤:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一步:代码中删掉data source和delegate相关代码。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第二步是将table view使用到的数据从数组,改为一个Observable:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"let europeanChocolates = Observable.just(Chocolate.ofEurope)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"just(_:)"}]},{"type":"text","text":"表明"},{"type":"codeinline","content":[{"type":"text","text":"Observable"}]},{"type":"text","text":"持有的值(value)是不变的。"}]},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"注:对于不变化的值,是没有必要使用响应式编程的。所以在实际应用中,要避免拿着锤子看什么都是钉子,要实际分析一下你是否真的需要使用Rx。"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第三步在"},{"type":"codeinline","content":[{"type":"text","text":"//MARK: - Rx Setup"}]},{"type":"text","text":"添加如下代码:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"func setupCellConfiguration() {\n //1\n europeanChocolates\n .bind(to: tableView\n .rx //2\n .items(cellIdentifier: ChocolateCell.Identifier,\n cellType: ChocolateCell.self)) { //3\n row, chocolate, cell in\n cell.configureWithChocolate(chocolate: chocolate) //4\n }\n .disposed(by: disposeBag) //5\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代码释义:"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"调用"},{"type":"codeinline","content":[{"type":"text","text":"bind(to:)"}]},{"type":"text","text":"将"},{"type":"codeinline","content":[{"type":"text","text":"europeanChocolates"}]},{"type":"text","text":"这个Observable关联到table view每行所要执行的代码上"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"调用rx后,让你可以访问table view的Rx扩展"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"代用Rx方法"},{"type":"codeinline","content":[{"type":"text","text":"items(cellIdentifier:cellType:)"}]},{"type":"text","text":"传递相关参数。这样Rx框架根据这些信息实现一个wrapper data source,再设置给table view。"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"当拿到了新的cell item时,通过这个block来进行设置"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"将bind方法结果加入到disposeBag"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"再在viewDidLoad中调用下上面的setupCellConfiguration()方法。"}]},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"这时运行程序,就会再次看到巧克力列表数据。"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/2a/2a092c225915e88d0e4c5e5d9a56fa96.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第四步来添加事件处理:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在//MARK: - Rx Setup位置添加如下代码"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"func setupCellTapHandling() {\n tableView\n .rx\n .modelSelected(Chocolate.self) //1\n .subscribe(onNext: { [unowned self] chocolate in // 2\n let newValue = ShoppingCart.sharedCart.chocolates.value + [chocolate]\n ShoppingCart.sharedCart.chocolates.accept(newValue) //3\n \n if let selectedRowIndexPath = self.tableView.indexPathForSelectedRow {\n self.tableView.deselectRow(at: selectedRowIndexPath, animated: true)\n } //4\n })\n .disposed(by: disposeBag) //5\n}\n"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代码释义:"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"modelSelected(_:)"}]},{"type":"text","text":"传入Chocolate model类型,得到Observable对象"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"codeinline","content":[{"type":"text","text":"subscribe(onNext:)"}]},{"type":"text","text":"是给Observable对象注册事件处理,这个闭包代码会在一个model被选中时调用"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"闭包中首先将选中的chocolate加入到购物车"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"反选table row"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"将"},{"type":"codeinline","content":[{"type":"text","text":"subscribe(onNext:)"}]},{"type":"text","text":"返回的"},{"type":"codeinline","content":[{"type":"text","text":"Disposable"}]},{"type":"text","text":"加入到"},{"type":"codeinline","content":[{"type":"text","text":"disposeBag"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"最后在viewDidLoad中调用"},{"type":"codeinline","content":[{"type":"text","text":"setupCellTapHandling()"}]}]},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"然后运行,就可以看到经过Rx API重写过的样例功能,和之前一样:点击列表中的巧克力,将他们添加到购物车中。"}]},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"使用RxSwift处理用户输入(Direct Text Input)"}]},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"RxSwift也可以用来处理用户输入和表单验证。"}]},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"非Reactive的做法是,实现"},{"type":"codeinline","content":[{"type":"text","text":"UITextFieldDelegate"}]},{"type":"text","text":",然后实现很多"},{"type":"codeinline","content":[{"type":"text","text":"if/else"}]},{"type":"text","text":"来分别处理输入及其对应的动作。"}]},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"Reactive Programming的做法则是更直接地将输入处理动作和逻辑绑定到input field上。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我们可以通过下面这个例子来具体体会一下。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先在"},{"type":"text","marks":[{"type":"italic"}],"text":"BillingInfoViewController.swift"},{"type":"text","text":"中创建一个DisposeBag:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"private let disposeBag = DisposeBag()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后在"},{"type":"codeinline","content":[{"type":"text","text":"//MARK: - Rx Setup"}]},{"type":"text","text":"评论下的extension中添加如下代码:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"func setupCardImageDisplay() {\n cardType\n .asObservable() //1\n .subscribe(onNext: { [unowned self] cardType in\n self.creditCardImageView.image = cardType.image\n }) //2\n .disposed(by: disposeBag) //3\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代码释义:"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"给"},{"type":"codeinline","content":[{"type":"text","text":"BehaviorRelay"}]},{"type":"text","text":"的值添加一个"},{"type":"codeinline","content":[{"type":"text","text":"Observable"}]}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"订阅这个"},{"type":"codeinline","content":[{"type":"text","text":"Observable"}]},{"type":"text","text":"来获得"},{"type":"codeinline","content":[{"type":"text","text":"cardType"}]},{"type":"text","text":"的变化"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"将Observer的销毁交给"},{"type":"codeinline","content":[{"type":"text","text":"disposeBag"}]}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"上述代码就实现了一个Reactive的例子:当cardType变化时,creditCardImageView的图片会变成对应type定义的图片。接下来实现对文字变化的处理。"}]},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"考虑到用户输入可能会很快,如果每次输入都执行验证代码可能会导致UI卡顿。所以我们需要实现一个throttle来控制,在throttle的时间间隔后才会再次验证输入,这样就可以避免对UI的阻塞。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RxSwift是支持Throttling的,因为有不少场景需要控制对变化的响应频次。下面我们来看看如何实现。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,在BillingInfoViewController中定义一个常量来表示throttle的间隔毫秒时间:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"private let throttleIntervalInMilliseconds = 100"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后,在"},{"type":"codeinline","content":[{"type":"text","text":"RX Setup"}]},{"type":"text","text":" extension中加入下面的代码:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"func setupTextChangeHandling() {\n let creditCardValid = creditCardNumberTextField\n .rx\n .text //1\n .observeOn(MainScheduler.asyncInstance)\n .distinctUntilChanged()\n .throttle(.milliseconds(throttleIntervalInMilliseconds), scheduler: MainScheduler.instance) //2\n .map { [unowned self] in\n self.validate(cardText: $0) //3\n }\n \n creditCardValid\n .subscribe(onNext: { [unowned self] in\n self.creditCardNumberTextField.valid = $0 //4\n })\n .disposed(by: disposeBag) //5\n}"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"代码释义:"}]},{"type":"numberedlist","attrs":{"start":1,"normalizeStart":1},"content":[{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"text是RxCocoa的扩展,它从一个UITextField获得其value的一个Observable"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"基于之前定义的throttle时间间隔,创建一个throttle;scheduler目前先绑定到主线程"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"将结果交由给"},{"type":"codeinline","content":[{"type":"text","text":"validate(cardText:)"}]},{"type":"text","text":",它可以将输入转换为creditCardValid的值,如果输入有效,creditCardValid的值会是true"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"监听creditCardValid,根据其结果来改变text field的状态"}]}]},{"type":"listitem","content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"销毁事宜"}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"实现CVV的校验和上述思路一样。"}]},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"先在"},{"type":"codeinline","content":[{"type":"text","text":"setupTextChangeHandling()"}]},{"type":"text","text":"底部添加如下代码:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"let expirationValid = expirationDateTextField\n .rx\n .text\n .observeOn(MainScheduler.asyncInstance)\n .distinctUntilChanged()\n .throttle(.milliseconds(throttleIntervalInMilliseconds), scheduler: MainScheduler.instance)\n .map { [unowned self] in\n self.validate(expirationDateText: $0)\n}\n \nexpirationValid\n .subscribe(onNext: { [unowned self] in\n self.expirationDateTextField.valid = $0\n })\n .disposed(by: disposeBag)\n \nlet cvvValid = cvvTextField\n .rx\n .text\n .observeOn(MainScheduler.asyncInstance)\n .distinctUntilChanged()\n .map { [unowned self] in\n self.validate(cvvText: $0)\n}\n \ncvvValid\n .subscribe(onNext: { [unowned self] in\n self.cvvTextField.valid = $0\n })\n .disposed(by: disposeBag)"}]},{"type":"paragraph","attrs":{"indent":0,"number":6,"align":null,"origin":null},"content":[{"type":"text","text":"最后,将三个text field结合起来做验证,在"},{"type":"codeinline","content":[{"type":"text","text":"setupTextChangeHandling()"}]},{"type":"text","text":"加入以下代码:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"let everythingValid = Observable\n .combineLatest(creditCardValid, expirationValid, cvvValid) {\n $0 && $1 && $2 //All must be true\n}\n \neverythingValid\n .bind(to: purchaseButton.rx.isEnabled)\n .disposed(by: disposeBag)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"combineLatest(_:)将三个observable结合到一起再生成一个observable,everythingValid的值会是true或false。接着再讲everythingValid关联到purchaseButton的rx扩展属性isEnabled,以此来实现对该button是否可用状态的控制。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最后,在viewDidLoad中添加如下代码:"}]},{"type":"codeblock","attrs":{"lang":"swift"},"content":[{"type":"text","text":"setupCardImageDisplay()\nsetupTextChangeHandling()"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"运行代码,然后选择巧克力后,进入购物车:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/39/39a791d7d5377fccaade7c3ad5f3d998.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然后点击checkout,进行支付界面:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f9/f96c9d0629033d01a19a522aa7a7dcf2.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"这里可以看到,输入4,后面图标就显示了visa的图标。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"接着,输入合法的CVV和有效期限,Buy Chocolate按钮就是可用状态了:"}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fd/fdcc447cec1297fef08c40a6505ef047.png","alt":null,"title":"","style":[{"key":"width","value":"50%"},{"key":"bordertype","value":"none"}],"href":"","fromPaste":false,"pastePass":false}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"至此,我们就实现了一个简单的对用户输入的reactive方式的校验实现了。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章