使用 RxSwift 进行响应式编程

分享到:  QQ好友和群QQ好友和群 QQ空间QQ空间 腾讯微博腾讯微博 腾讯朋友腾讯朋友 微信微信
查看查看738 回复回复5 收藏收藏2 分享淘帖 转播转播 分享分享1 微信
查看: 738|回复: 5
收起左侧

使用 RxSwift 进行响应式编程

[复制链接]
苏格拉没有底 发表于 2016-9-29 18:23:07 | 显示全部楼层 |阅读模式
快来登录
获取优质的苹果资讯内容
收藏热门的iOS等技术干货
拷贝下载Swift Demo源代码
订阅梳理好了的知识点专辑
您或许曾经听说过「响应式编程」(Reactive Programming) 一词,甚至很可能研究过 RxSwift 的相关内容。但是如果您没有在日常开发中使用响应式编程的话,那么您就真的落后于时代了!在 AltConf 2016 的本次讲演中,Scott Gardner 将带大家走入到响应式编程的世界当中,并告诉大家一个简单的方法来开始学习 Reactive Swift。他会对 RxSwift 进行一个大致的介绍,同时还会介绍其他的学习资料来帮助您更好的学习 RxSwift。快来看一看使用 RxSwift 进行响应式编程是如何改变您的编码方式,这将超乎您的想象。

使用 RxSwift 进行响应式编程 0

使用 RxSwift 进行响应式编程 - 敏捷大拇指 - 使用 RxSwift 进行响应式编程 0


https://embedwistia-a.akamaihd.net/deliveries/47fb3d52e4724b99cd05f35b3178bda3d7944981/file.mp4




1、概述 (0:00)

我很高兴今天能在这里介绍响应式编程,尤其是关于 RxSwift 的内容。在我开始之前,请准许我向诸位介绍一下我自己。

我的名字是 Scott Gardner,我已经有 6 年的 iOS 开发经验了,对于 Swift 来说是 2 年。我写的其中一本关于 Swift 的书是关于如何帮助 Objective-C 开发者迁移到 Swift 的。我写过也教过很多教程,我在 CocoaConf 以及其他会议上演示过 Swift 和 iOS 的相关内容,在 St. Louis 的华盛顿大学也做过讲演,我还为 Ray Wenderlich 撰写过文章,现在我是 Lynda.com 的一名签约作家。我同时也是 RxSwift 项目的贡献者之一,并且就如 Alex 所说,我会在本月晚些时候去 The Weather Channel 就职,这是隶属于 IBM 的一家公司,我将成为他们那儿的 iOS 工程团队的管理者。

今天的这次讲演,将是关于如何使用一个名为 RxSwift 的响应式第三方库的,它会为您完成诸多地繁重工作,从而让编程更加轻松。




2、传统式与响应式大对比 (1:36)

我觉得我应该需要用一个例子,来向各位直观地展示一下 RxSwift 能够做些什么。

我们这里要大致说明一下这个例子,这是一个 iOS 应用,我将创建一个 Speaker 的结构体,这只是一个存储讲演者名字、Twitter 和头像的简单数据结构,此外它还遵循 CustomStringConvertible 协议。

[Swift] 纯文本查看 复制代码
import UIKit

struct Speaker {
    let name: String
    let twitterHandle: String
    var image: UIImage?
    
    init(name: String, twitterHandle: String) {
        self.name = name
        self.twitterHandle = twitterHandle
        image = UIImage(named: name.stringByReplacingOccurrencesOfString(" ", withString: ""))
    }
}

extension Speaker: CustomStringConvertible {
    var description: String {
        return "\(name) \(twitterHandle)"
    }
}


接下来,我们要写一个 ViewModel。

[Swift] 纯文本查看 复制代码
import Foundation

struct SpeakerListViewModel {
    let data = [
        Speaker(name: "Ben Sandofsky", twitterHandle: "@sandofsky"),
        Speaker(name: "Carla White", twitterHandle: "@carlawhite"),
        Speaker(name: "Jaimee Newberry", twitterHandle: "@jaimeejaimee"),
        Speaker(name: "Natasha Murashev", twitterHandle: "@natashatherobot"),
        Speaker(name: "Robi Ganguly", twitterHandle: "@rganguly"),
        Speaker(name: "Virginia Roberts",  twitterHandle: "@askvirginia"),
        Speaker(name: "Scott Gardner", twitterHandle: "@scotteg")
    ]
}


不过我不想让本次讲演变成了关于 MVVM 或者其他诸如此类的架构模式的讲演,因此我不会着重介绍这一部分。我在这里所做的就是创建了一个会被 UITableView 所使用的数据源而已,并且我将它提取出来,让其作为一个能够创建数据的单独数据类型。接下来,我们会在持有 UITableView 的视图控制器当中使用这个类型,因此这只是对代码做了一点小小的变化而已。接下来在视图控制器当中,我将要实现标准的 UITableViewDataSource 和 UITableViewDelegate 协议,就如同我们以往所做的那样。这些代码完完全全就是样板代码 (boilerplate code),我们大家都已经一遍又一遍地写过这些代码了。我们最终的效果就是一个 UITableView,它列出了部分本周在这里进行讲演的演讲者信息。

[Swift] 纯文本查看 复制代码
class SpeakerListViewController: UIViewController {
    
    @IBOutlet weak var speakerListTableView: UITableView!
    
    let speakerListViewModel = SpeakerListViewModel()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        speakerListTableView.dataSource = self
        speakerListTableView.delegate = self
    }
}

extension SpeakerListViewController: UITableViewDataSource {
    
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int)-> Int {
        return speakerListViewModel.data.count
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        guard let cell = tableView.dequeueReusableCellWithIdentifier("SpeakerCell")
            else {
                return UITableViewCell()
        }
        
        let speaker = speakerListViewModel.data[indexPath.row]
        cell.textLabel?.text = speaker.name
        cell.detailTextLabel?.text = speaker.twitterHandle
        cell.imageView?.image = speaker.image
        return cell
    }
}

extension SpeakerListViewController: UITableViewDelegate {
    func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        print("You selected \(speakerListViewModel.data[indexPath.row])")
    }
}


好的,现在我将把这个项目 Rx 化,首先第一步我要做的就是修改 ViewModel,将 data 属性变成一个可观察序列对象 (Observable Sqquence)。接下来我会好好解释一下这个「可观察序列对象」是什么的,但是现在各位只要知道,这个序列对象当中的内容和我们之前在数组当中所包含的内容是完全一模一样的。「序列」可以对这些数值进行「订阅」(Subscribe),就如同 NSNotificaitonCenter 的概念差不多。

[Swift] 纯文本查看 复制代码
import RxSwift

struct SpeakerListViewModel {
    let data = Observable.just([
        Speaker(name: "Ben Sandofsky", twitterHandle: "@sandofsky"),
        Speaker(name: "Carla White", twitterHandle: "@carlawhite"),
        Speaker(name: "Jaimee Newberry", twitterHandle: "@jaimeejaimee"),
        Speaker(name: "Natasha Murashev", twitterHandle: "@natashatherobot"),
        Speaker(name: "Robi Ganguly", twitterHandle: "@rganguly"),
        Speaker(name: "Virginia Roberts",  twitterHandle: "@askvirginia"),
        Speaker(name: "Scott Gardner", twitterHandle: "@scotteg")
    ])
}


因此,我们回到视图控制器当中,我们现在就不用去实现数据源和委托协议了,这里我写了一些响应式代码,它们将数据和 UITableView 建立了绑定关系。

[Swift] 纯文本查看 复制代码
import RxSwift
import RxCocoa

class SpeakerListViewController: UIViewController {
    
    @IBOutlet weak var speakerListTableView: UITableView!
    
    let speakerListViewModel = SpeakerListViewModel()
    let disposeBag = DisposeBag()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        speakerListViewModel.data
            .bindTo(speakerListTableView.rx_itemsWithCellIdentifier("SpeakerCell")) { _, speaker, cell in
                cell.textLabel?.text = speaker.name
                cell.detailTextLabel?.text = speaker.twitterHandle
                cell.imageView?.image = speaker.image
            }
            .addDisposableTo(disposeBag)
        
        speakerListTableView.rx_modelSelected(Speaker)
            .subsribeNext { speaker in
                print("You selected \(speaker)")
        }
        .addDisposableTo(disposeBag)
    }
}


我现在会对这段代码逐行进行解释。

我添加了一个名为 DisposeBag 的玩意儿,这是 Rx 在视图控制器或者其持有者将要销毁的时候,其中一种实现「订阅处置机制」的方式。这就和 NSNotificationCenter 的 removeObserver 类似。

因此,「订阅」将在对象销毁的时候进行处置,它会负责清空它里面的资源。接下来我们使用了 rx_itemsWithCellIdentifier,这是 Rx 基于 cellForRowAtIndexPath 数据源方法的一个封装。此外,Rx 也完成了对 numberOfRowsAtIndexPath 方法的实现,这个方法在传统方式当中是必不可少的,但是这里我们就没必要实现它了,Rx 已经帮我们完成了。

接下来是这个 rx_modelSelected,它是 Rx 基于 UITableView 委托回调方法 didSelectRowAtIndexPath 的一个封装。

好的,这就是全部了。然后来对比一下传统方式和响应式的异同吧!



2.1、比较结果 (4:07)

实际上,您会发现您已经精简了将近 40% 的代码量,而这些所精简的代码基本上就是我们之前所说的那些「样板代码」。如果我们换个角度比喻的话,这就像我们只需要工作到午餐后,但是却能够拿一天的薪水。或者,对于我们这些在美国工作的人来说,就相当于工作到 7 月 4 号,然后剩下的日子就可以去享受带薪假期了。

是不是感觉很心动呢?

这只不过是一个很简单的例子而已,但是我们所说的绝对不只是为了精简代码而已。我们所要做的,就是要编写更具有表现力的代码,尤其是在编写异步代码的时候。




3、何为响应式编程? (4:45)

那么现在各位可能就会在想了,何为响应式编程?

响应式编程的关键在于:将异步可观察序列对象模型化。这些序列对象可以包含值,就如同我在上一个例子中给大家展示的那样,但是这些序列对象同时也可以是所谓的「事件流」,比如说单击或者其他手势事件。其他所有东西都是建立在这个概念的基础上的。因此,响应式编程的概念已经存在了 20 年了,但是直到近年它才有了极大的发展,比如说 2009 年所引入的响应式扩展 (Reactive Extension)。

因此,RxSwift 是一系列标准操作符的集合,这些操作符涵盖了所有的 Rx 实现,它可以被用来创建并与可观察序列对象协同工作。

当然,我们还有专门面向平台的第三方库,比如说我在上个例子为大家展示的 RxCocoa,它是专门为 iOS、OS X 之类平台所构建的。RxSwift 是针对 Swift 响应式扩展的官方实现,每个实现方法都实现了在不同语言和不同技术平台上相同的模式和操作符。



3.1、无处不序列 (5:58)

在 Rx 中,基本上所有东西要么是一个可观察序列对象,要么就是需要和可观察序列对象进行协同工作的。因此,序列对象将会按需推出其中的内容,这些内容都属于技术事件。您可以订阅一个可观察序列,以便对推出的这些事件作出响应。再强调一遍,这个机制和 NSNotificationCenter 极其类似,但是 Rx 更加优秀。

RxSwift 操作符将执行各种任务,它们是基于事件的。它们通常以异步方式执行,此外 Rx 还是函数式的,因此您或许会采用函数式响应式编程 (Functional Reactive Programming, FRP) 模式,我对这个模式是很满意的,我觉得这是 RxSwift 应有的样子。其他的话我就可能不这么看了。



3.2、Rx 模式 (5:19)

也就是说,Rx 实现了两种常见的模式:

  • 首先是观察者模式 (Observer),它是管理一系列其从属单元 (Dependents) 的对象,其中包括了观察者和订阅者 (Subscriber),一旦发生变化就会发送通知。
  • 此外是迭代模式 (Iterator),这样集合或者序列中的值就可以进行遍历了。


因此,Rx 对于绝大多数现代编程语言来说都是可以实现的。因此,让我们现在来谈论一下某个可观察序列对象的生命周期吧!



3.3、可观察序列对象的生命周期 (7:25)

这个箭头表示可观察序列对象随时间变化的情况,当某个值或者一系列值被放到序列对象当中的时候,它就会给它的观察者发送一个「下一步 (Next)」事件,而这个事件当中将会包含这些新增的元素。再次强调一遍,这个过程称之为发送 (Emitting),而那些值将变成元素 (Element)。不管这里的这些值是什么,比如说触摸事件、点击事件、TouchInside 事件还是什么,它们的工作方式都是相同的。(顺便提一点,这被称之为 Marble 图。)

如果遇到了错误,那么序列对象将会发送一个错误事件 (Error Event),这其中将包含有错误类型实例,这样您就可以对这个事件做出回应,以便执行错误处理,问询该错误,以便查看哪里出了问题。当错误发生之后,也就是这条时间线上的 X 表示的地方,它同样会立刻终止这个序列,当序列中止之后,它就没办法再发送更多的事件了,因此一旦您接取到了错误事件,就意味着这个序列已经死掉了。

使用 RxSwift 进行响应式编程 1

使用 RxSwift 进行响应式编程 - 敏捷大拇指 - 使用 RxSwift 进行响应式编程 1

lifecycle map

序列也可以正常终止,而当序列正常终止之后,它将会发送一个完成事件 (Completed Event),也就是时间线上的竖线表示的地方。

好的,这部分说完了。

也就是说,我在这儿说了很多关于 RxSwift 的内容,不过讲道理,当我在谈论 RxSwift 的时候,我实际上是指一个更大的功能集,不仅仅包含了 RxSwift 核心库,还包含了 RxCocoa(这是专门为 iOS、OS X、watchOS 和 tvOS 平台而专门实现的响应式扩展)。目前为止,RxSwift 社区仓库当中已经有很多可用的响应式第三方库了。比如说有 RxDataSources 库。它借助响应式扩展来为 UITableView 和 UICollectionView 提供了多种更友善的使用方式。




4、跟随我的脚步 (9:50)

如果您想要跟随我来一起浏览 RxSwift 的话,您可以根据以下说明,去获取 CocoaPods Playgrounds 插件,这可以创建一个特殊的 Playground,它可以将诸如 RxSwift 之类的第三方库拉取到其中。

  • 安装 ThisCouldBeUsButYouPlaying
  • 创建 RxSwiftPlayground
  • pod playgrounds RxSwift


我首先做的事情就是创建一个封装我的示例的辅助函数。它会将描述信息打印出来,然后运行示例代码。

[Swift] 纯文本查看 复制代码
//Please build the scheme 'RxSwiftPlayground' first
import XCPlayground
XCPlaygroundPage.currentPage.needsIndefiniteExecution = true

import RxSwift

func exampleOf(description: String, action: Void -> Void) {
   print("\n--- Example of:", description, "---")
   action()
}


非常简单,对吧?




5、创建并订阅可观察序列 (10:00)

我们的第一个例子将要来使用 just 操作符,我们将创建一个包含单个值的可观察序列,在这个例子中将是一个整数值。接下来,我会订阅一个事件,这个事件随后将通过 subscribe 操作符从这个可观察序列当中传送出去。每当我接收到事件对象,我都会将其输出到控制台上,这里我使用 $0 这个默认参数名。

[Swift] 纯文本查看 复制代码
exampleOf("just") {
   Observable.just(32)
   .subscribe {
        print($0)
   }
}


也就是说,首先我们可以获取包含该元素的「下一步事件」,接着我们就可以获取「完成事件」。和错误事件类似,一旦某个可观察序列发送了完成事件,那么这个序列将自动终止。它将无法再次发送任何元素。

[Swift] 纯文本查看 复制代码
exampleOf("just") {
   _ = Observable.just(32)
   .subscribeNext {
     print($0)
   }
}


如果您对 Swift 不熟悉的话,只需要知道,$0 是一个默认参数。只要我想,我就可以像这样显式地命名出来,但是如果闭包中只有一两个参数的话,那么我更倾向于使用诸如 $0、$1 之类的默认参数名。这种命名方式更简单、更方便。更多Swift知识,还是请每天访问Swifthumb.com敏捷大拇指,全球最大的Swift开发者社区。

如果您这个时候观察到 Playground 向您报了一个警告,那是因为订阅实际上会返回一个表示该订阅对象的值,这是一个 Disposable 的类型,但是我们现在还没有对其进行任何处理。

[Swift] 纯文本查看 复制代码
exampleOf("just") {
    Observable.just(32)
    .subscribe { element in
       print(element)
    }
}


首先我将要小小地修改一下这个例子,只需要指明忽略此值即可。接下来要注意到,这个时候我将换用 subscribeNext 操作符。subscribeNext 只会监听下一个事件,并且它返回的只是元素,而不是包含元素的事件。因此,我们现在只需要输出这个元素即可。

[Swift]