RxSwift 1 基础

这里有一个非常好的库, 主要是它的 README, 可以帮助理解 Rx 的内部运行机制.

先来看 一段同步代码:

    var arr = [1, 2, 3]
    print(arr)
    for num in arr {
      print(num)
      arr = [4, 5, 6]
    }
    print(arr)

打印的结果如下:

    [1, 2, 3]
    1
    2
    3
    [4, 5, 6]

数组在 for 语句中是不可变的. 就是说循环中 arr 的值并没有被改变. 当退出循环后才改变了 arr 的值.

上述代码的执行顺序实际是: 先遍历了整个数组, 然后再对数组进行赋值.

上述代码的打印信息是可以被预测的, 因为执行顺序是严格按照一定顺序执行的.

但如果是想在循环中改变数组中元素的值应该怎么做呢?

再来看一段异步代码:

    var array = [1, 2, 3]
    var currentIndex = 0
        
    @IBAction func btnClicked(_ sender: UIButton) {
      print(array[currentIndex])
      if currentIndex != array.count - 1 {
        currentIndex += 1
      }
    } 

由于这个数组可以同时被任意代码访问, 只要有另外一段代码在下次点击之前改动了数组, 那打印出来的东西就是不可预测的.

而且, 如果另外一段代码在下次点击前将 currentIndex 改变成了一个非法的值, 那么就会引起崩溃.

但是, 我们程序中有大量的情况都是这样的:

  • 许多段代码的执行顺序不确定, 它们的执行是受外界驱动的.
    比如说用户的操作, 网络的响应信息, 或者是蓝牙的数据过来.(这样的多种驱动因素集合到一起, 如果没有有效的编程手段, 将十分难以保证程序的正确运行.)
  • 代码共享同一个可变数据源.
    任意一段或多段代码都可以去修改这个数据源, 并且被修改的顺序是不可预测的.

这样的情况就是程序混乱的根源. 不过不要担心, RxSwift 最擅长的就是解决上述问题.

异步编程术语

总地来说, RxSwift 尝试解决的是如下的问题:

  • 状态管理, 尤其是共享的可变状态
    由于状态可能会被任意的异步执行代码所改变, 故尤其需要注意状态管理.
  • 命令式编程
    是一种编程范式, 是指通过表达式的形式来改变程序状态. 即使用命令来指定程序将在何时将状态改变为什么.
    比如下面的代码:
        override func viewDidAppear(_ animated: Bool) {
          super.viewDidAppear(animated)
          setupUI()
          connectUIControls()
          createDataSource()
          listenForChanges()
        }
    
    这些代码的实际作用虽然可以一看就懂. 但是并不知道在这些代码中是否改变了控制器的状态? 或者说, 它们的执行顺序在任何情况下都是正确的吗? 比如将调用顺序进行更换, 有可能就会出现十分不同的程序行为.
  • 副作用
    实际上状态管理和命令式编程所要解决的绝大部分的问题都可以被归为 副作用.
    比如在当前的范围内不想去改变应用的状态, 但在当前的一些代码却给其他异步代码打开了口子, 让它们可以在当前范围内去改变程序的状态, 这个就是副作用. 上述代码的connectUIControls就是一个绝佳的例子, 假设在里面将某个UI 和某个事件处理函数绑定, 则有执行这段代码和没有执行这段代码时候的程序行为是完全不同的.
    实际上副作用并非是不好的东西, 因为所有的应用最终的目的都是要实现各种的 "副作用": 因为程序本身就是要在执行完成之后会改变外界的一些东西. 如果一个程序最终运行完了, 却对任何东西没有任何影响, 那这个程序就是无用的程序.
    我们希望的是让副作用以可控的方式呈现!
  • 声明式代码:
    在命令式编程中改变程序的状态, 而在函数式编程中不去引起任何的副作用. 而连接二者的桥梁就是声明式代码.
    声明式代码定义了许多功能块, 无论什么时候, 只要有相应事件发生时, RxSwift 就会执行这些功能块, 并为它们提供不可变且各自独立的数据作为输入.
    这样的结果就如同最开始的 for 循环, 代码以一种可预测的方式执行.
  • 响应式的系统
    响应式系统这个名词实际是比较抽象的, 它涵盖了从 web 到 iOS 中如下内容:
    • Responsive(响应): UI 显示的永远都是最新的更新, 即永远展示的是程序的最新状态.
    • Resilient(可复原): 功能都是独立定义的, 并且提供了灵活的错误回复能力.
    • Elastic(弹性): 代码提供多种负载处理功能, 通常有集合懒加载拉取, 事件减速(throttle), 资源共享等.
    • 消息驱动: 构件间基于消息系统进行通信, 这样可以降低耦合, 提高复用能力, 并且将类的生命期和其实现进行分离.

下面来看看 RxSwift 的基本组成及基本使用.

RxSwift 基础

由于程序中需要管理大量可能引起异步事件的 UI, 实际也就引起大量的异步代码的执行, 让程序状态管理变得越来越困难, 因而响应式编程被提出并得到极大发展.

在这样的背景下, 微软的一个团队潜心打造出来了第一个响应式编程框架 Reactive Extensions for .NET (Rx), 当时差不多是 2009 年. 然后在 2012 年的时候开源.

这样, 就允许更多的编程语言可以去实现相同的响应式功能. 目前, 有大量的语言都实现了相同功能的响应式框架, 比如 RxJava, Rx .NET, RxJS 等等. 到再后来, Rx 就形成了一个跨平台的响应式框架标准. RxSwift 也是该标准的实现之一.

RxSwift 在传统的命令式代码(副作用)和纯函数式代码(无副作用)之间作为桥梁, 允许开发人员使用不可变数据作为异步代码的输入, 来对事件作出响应. 让异步代码以一种可预测, 可聚合的方式执行.

RxSwift 中的三大构件(也可以说是 Rx 标准中通用的三大概念):

  • observable
  • operator
  • scheduler

下面分别来看看.

Observable

Observable<T> 类是 Rx 代码的基础构件, 它能够异步地产生事件序列, 序列包含的是 T 类型数据的不可变版本. 如果某个类随时间不断发射数据, 则这些数据就被包含在事件序列中, 并且可以被另外的类观察到.

Observable 可被一个或多个观察者实时观察, 从而可以根据数据来对应更新 UI, 或者是处理及利用新的数据.

Observable 类实现了 ObservableType 协议, 该协议十分简单, 它只允许 Observable 发射三种类型的事件:

  • next 事件: 在这类事件中携带的是最新的或"下一个"数据, 观察者就是通过该事件来获取数据.
  • completed 事件: 表示结束 Observable 的事件序列成功结束, 并且之后Observable 将不再发射任何事件序列.
  • error 事件: 表示 Observable 的事件序列非正常结束, 同样 Observable 将不再发射任何事件序列.

事件序列的类型有如下两种:

  • 有限的 Observable 序列:
    有限的 Observable 事件序列发射0个或多个值, 在未来的某个时候, 序列则会成功结束或非正常结束.
    比如 iOS 中在下载文件的时候:
    • 首先启动下载, 并开始观察下载的数据
    • 数据开始按块进行接收, 即一个个数据块随时间增长被接收到.
    • 当发生网络中断事件, 下载就会停止, 这时整个过程就会以 error 结束.
    • 若没有任何错误, 接收到了所有的数据, 则整个过程会以 complete 结束.
      上面的下载过程实际就可以形成一个有限序列, 而处理代码如下所示:
        API.download(file: "http://www...")
        .subscribe(onNext: { data in
          ... append data to temporary file
        },
        onError: { error in
          ... display error to user
        },
        onCompleted: {
          ... use downloaded file
        })
    
    其中的 API.download 会返回一个 Observable<Data> 类型的对象, 这个对象就可以发射 Data 值序列, 直至最后或是以 error 结束, 或是以 complete 结束.
    然后通过 subscribe(观察) 它的 next 事件, error 事件, complete 事件, 从而可以在不同的事件发生时执行不同的命令.
  • 无限的 Observable 序列:
    还有一些事件序列是无限的, 即不会在未来的某个时候发射 complete 或 error. 典型的就是 UI 产生的一些事件.
    比如代码中要响应屏幕旋转:
    • 首先去观察屏幕旋转通知
    • 接收到通知后, 去读取当前屏幕旋转到的最新的值, 从而根据这个值来执行不同的操作.
      类似上面的情况, 这些事件序列不可能在未来有一个合理的终点. 这样就形成了无限事件序列.
      并且由于序列是无限的, 故总是可以在开始观察的时候接收到一个初始值. 这点很重要.
      而当用户一直不旋转屏幕的情况下, 也不代表这个序列是有限的, 因为始终在未来的某个时间, 可能会旋转屏幕, 这点是不能被绝对否定的. 所以不能认为该序列就终止了. 此时只能认为该序列暂时没有发射新数据.
      对于无限序列, 可以通过下面的代码来处理:
        UIDevice.rx.orientation
        .subscribe(onNext: { current in
          switch current {
            case .landscape:
              ... re-arrange UI for landscape
            case .portrait:
              ... re-arrange UI for portrait
          }
        })
    
    代码中 UIDevice.rx.orientation 会返回一个Observable<Orientation> 对象, 该对象发射的就是选择事件, 并且带有当前最新的旋转值, 此时就可以观察 next 事件并进行处理.

Operator

这里的 Operator 可以对事件序列进行操作, 比如过滤 filter, 映射(map) 等, 让序列在最终送到观察者之前进行处理.

Scheduler

这里 Scheduler 相当于是 RxSwift 里面的 dispatch queue. 但是要比 dispatch queue 更加强大易用. 这个后面会讲到.

APP 架构

需要指出的是, RxSwift 并非为特定架构存在的. 可以在MVC, MVVM, MVP等等等等架构中使用, 换句话说, 它和架构没有任何关系, RxSwift 只是负责处理事件, 异步数据流, 以及数据交互.

安装 RxSwift

当然可以通过 pod 或 carthage.

其中的 RxSwift 实现了 Rx 的通用 API, RxCocoa 负责实现了和 Cocoa 框架相关的专用 API. RxTest 和 RxBlocking 用于单元测试.

可以先克隆 RxSwift 的源码, 在里面有 Rx.playground, 它里面有许多 operator 的例子. 另外就是 RxExample, 里面有许多优秀的实际工程.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,491评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,856评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,745评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,196评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,073评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,112评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,531评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,215评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,485评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,578评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,356评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,215评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,583评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,898评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,174评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,497评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,697评论 2 335

推荐阅读更多精彩内容