RxSwift For Dummies 🐣 Part1

网络图片

RxSwift 真的是一个非常值得学习的东西。非常遗憾的是我没有研究所有的架构模式(MVVM VIPER Routing)。

要非常好的讲出来RxSwift到底是个什么东西,我也说不好。毕竟他能做太多的事情了。普遍认为,他是函数响应式编程中非常重要的观察者模式。在最初的定义中,他并不就是函数响应式编程。他最初的设计灵感就是来自于函数响应式(FRP), 所以也可以说它包含了函数响应式的特性。

如果你不知道什么是 FRP 的话, 不用担心, 在这个教程中你会自然而然的理解什么是 FRP

通过对 RXSwift 的深入研究, 我得到了很多的启发, 同时也被很多的问题困扰。相信你也会这样。

需要花很多个小时的时间来适应新的思维模式,唯一能确定的是,一旦你适应了,你就再也不想回到从前了。

在这个教程中,我会尽可能的节约你的时间,并且解释的尽可能的详细。想教幼儿园的小朋友一样。

开始学习之前, 请确定你已经掌握了 Swift 和 UIkit 的基础知识.

The Why?

写 UI 的时候经常会处理一些异步的操作。我们很早就知道要使用观察者模式来实现这个东西。我相信你现在已经非常熟悉代理模式了。代理模式是一种很酷的设计模式。但是写起来真的很烦😡。

<center>



</center>

  • 代理模式需要些很多的模版代码: 创建一个协议, 声明一个 delegate 变量, 遵守协议, 设置代理...
  • 写完这么多模版代码,可能你不小心就忘了其中的某个步骤。 比如说 object.delegete = self
  • 管理起来非常麻烦。他需要在好几个文件中跳跃。

RxSwift 解决了这个问题。他能够让你通过声明的方式使用观察者模式。减少了管理的负担, 当然,也不用写那么多模版代码。

我刚刚开始了一个项目,在这个项目中,至今还没有写一个 delegate

Basic Example

talk is cheap, show you the code.

class ExampleClass {
    let disposeBag = DisposeBag()
    
    func runExample() {
        // OBSERVABLE //
        let observable = Observable<String>.create { (observer) -> Disposable
            DispatchQueue.global(qos: .default).async {
                Thread.sleep(forTimeInterval: 10)
                observer.onNext("Hello dummy 🐣")
                observer.onCompleted()
            }
            return Disposables.create()
        }
        // OBSERVER //
        
        observable.subscribe(onNext:{ (element) in
            print(element)
        }).addDisposableTo(disposeBag)
    }
}

这就是最基本的例子, 在这个示例中, 我们声明了一个 runExample 方法。在这个方法中执行的是一些 RxSwift 中的事情。想一下在这个例子中发生了什么吧。

Observable 📡

我们还是从 RxSwift 中最基本的构建单元开始吧。 Observable。 它其实非常的简单。 Observable 执行某些动作, 然后观察者能够对此作出一些反应。

 let observable = Observable<String>.create { (observer) -> Disposable
    DispatchQueue.global(qos: .default).async {
        // Simulate some work
        Thread.sleep(forTimeInterval: 10)
        observer.onNext("Hello dummy 🐣")
        observer.onCompleted()
    }
    return Disposables.create()
}
                
observable.subscribe(onNext:{ (element) in
    print(element)
}).addDisposableTo(disposeBag)

现在我们有了一个 Observable 信号了。这种信号只有在被订阅之后才会执行它也被叫做:冷信号❄️。相反热信号🔥是那种既是没有被订阅也会执行的信号。

在下一步们我们会具体的讲解二者的区别。现在你只需要理解的是: 因为你初始化出来的是一个冷❄️信号Hello dummy 🐣这个值是不会被发送出来的。冷信号❄️只有在有东西订阅之后才会发送消息。

我们一步一步的来分析一下这究竟是什么意思。

DispatchQueue.global(qos: .default).async {...}

这行代码保证这个 Observable 信号在主线程中发送消息。其实 RxSwift 是有一个调度机制, 但是我现在还不想那么早告诉你, 不然你该记不值了。

observer.onNext("Hello dummy 🐣")

一个 Observable 信号发出的消息从时间上来看, 可以被看作是一个 序列。在这个序列中可能有无限多的值。我们可以通过 onNext 方法类将这些值发送出来。

observer.onCompleted()

当这个序列已经发送完了所有的值之后,它可以发送一个 Completed 或者 Error出来。之后这个信号就不能在产生更多的值了, 然后就会随着一个闭包被释放掉。

return Disposables.create()

每一个 Observable 信号都要返回一个 Disposable.

使用 Disposables.create() 如果你不想在信号被释放的时候处理其他事情。你可以看看NopDisposable的实现,你会发现,他什么事情都没有做,只是一些空方法。

Disposable

Disposable 对象必须要在 Observable 中返回, 它是用来在Observable不能再正常的完成的时候清除掉这些信号的。比如说你可以使用 AnonymousDisposable:

return Disposables.create(with: {
    connection.close()
    database.closeImportantSomething()
    cache.clear()
})

只有当信号被提前释放或者程序手动调用了 dispose() 方法, Disposable 才会被调用。但是在多数情况下, dispose() 方法都是通过 Dispose Bags 自动调用的。别着急,你可以在一些更具体的例子中自己实现这个东西。

Observer 🕵

我们创建的 Observable 是冷信号❄️。 除非我们订阅了它,不然它是不会发送信号的。

let disposeBag = DisposeBag()

...

observable.subscribe(onNext: {(element) in
  print(element)
}).addDisposableTo(disposeBag)

这就是订阅信号的方法。在 subscribeNext 方法中一个订阅就发生了。这个方法也会返回一个 Disposable.这个 Disposable 就是对这个订阅的记录

这个 Observable 就开始工作了, 10秒之后, 你就会在控制台看见

Hello dummy 🐣

subscribe(onNext:) 只会在Next事件发送出来的时候响应。也可以使用 subscribe(onCompleted:)subscribe(onError:) 响应对应的事件。

Dispose Bag 🗑

唯一一个还有点神秘的东西就是 addDisposableTo 这个方法了。

Dispose bags are used to return ARC like behavior to RX. When a DisposeBag is deallocated, it will call dispose on each of the added disposables.

Dispose bags 就像是一个垃圾筐。就像是 AutoreleasePool 一样,当这个垃圾筐被释放的时候, 里面的所有东西都会被释放掉。

当你订阅一个信号的时候, 你就需要把你创建出来的 Disposable 添加到这个框里面。当这的框被释放的时候(ExampleClass 对象 dealloc 的时候)。这些没有执行完的Disposable就会被释放掉。

它被用作释放在闭包中引用的值, 以及没用的资源, 比如说, 一个 HTTP 网络连接, 数据库连接, 或者是缓存的对象。

如果你还是不懂, 一会儿再举一个例子。

Observable operators

create 只是信号诸多操作方法中的一个而已,它被用来创建一个新的信号。可以看一下 ReactiveX
官方文档。哪里有所有的操作方法。我只是举一些常见的例子。

Just

let observable = Observable<String>.just("Hello again dummy 🐥");
observable.subscribe(onNext: { (element) in
    print(element)
}).addDisposableTo(disposeBag)
        
observable.subscribe(onCompleted: { 
    print("I'm done")
}).addDisposableTo(disposeBag)

Hello again dummy 🐥
I'm done

Just just 创建了一个智能释放一个值的信号。所以在这个信号序列中的事件,是这样的:

.Next("Hello") ->  .Completed

Interval

let observable = Observable<Int>.interval(0.3, scheduler: MainScheduler.instance)
observable.subscribe(onNext: { (element) in
   print(element)
}).addDisposableTo(disposeBag)

0
1
2
3
...

Interval 是一个非常具体的操作符号。在这个例子中, 它从 0 每0.3秒递增, scheduler 是用来定义异步行为的。

Repeat

let observable = Observable<String>.repeatElement("This is fun 🙄")
observable.subscribe(onNext: { (element) in
   print(element)
}).addDisposableTo(disposeBag)

This is fun 🙄
This is fun 🙄
This is fun 🙄
This is fun 🙄
...

repeat 会无限的重复我们给定的值。你可以通过定义 scheduler 类型的方法来控制线程的行为。

目前为止, 可能都不是非常的给力。但是知道其他的操作是必要的。另外一件很重要的事情涘,这是 RxSwift 最有用的一部分。

Real life example

现在我们开始快速的通过一个例子巩固一下这些知识。我们对 RxSwift 的了解目前为止是非常有限的。所有我们先使用一个简单的 MVC 的例子。我们先创建一个模型, 它可以从 google 上获取数据。

import Foundation
import RxCocoa
import RxSwift

final class GoogleModel {
    
    func createGoogleDataObservable() -> Observable<String> {
        return Observable<String>.create({ (observer) -> Disposable in
            
            let session = URLSession.shared
            let task = session.dataTask(with: URL(string: "https://www.google.com")!) { (data, response, error) in
                
                // 我们需要在主线程中更新
                DispatchQueue.main.async {
                    if let err = error {
                        // 如果请求失败, 直接发处失败的事件
                        observer.onError(err)
                    } else {
                        // 解析数据
                        if let googleString = String(data: data!, encoding: .ascii) {
                            // 将数据发送出去
                            observer.onNext(googleString)
                        } else {
                            // 如果解析失败发送失败的事件
                            observer.onNext("Error! Unable to parse the response data from google!")
                        }
                        // 结束这个序列
                        observer.onCompleted()
                    }
                }
            }
            task.resume()
            
            // 返回一个 AnonymousDisposable
            return Disposables.create(with: {
                // 取消请求
                task.cancel()
            })
        })
    }
}

这是非常简单的。 createGoogleDataObservable中我们创建了一个可以被订阅的信号。这个信号创建了一个从 google 获取数据的任务。

DispatchQueue.main.async {...}

URLSession 的任务是在后台线程中进行的, 所以我们需要在 UI 线程中更新。记住还有一个 schedulers 这会在更高级的阶段介绍出来。

return Disposables.create(with: {
 task.cancel()
})

Disposable 是一个给长给力的机制: 如果订阅者停止订阅这个信号了。这个任务就会被取消。

接下来是订阅者这部分的内容了。

import UIKit
import RxCocoa
import RxSwift

class ViewController: UIViewController {


    // 通常就是这样创建 DisposeBag 的
    // 当这个 controller 被释放掉的时候,disposebag
    // 也会释放掉, 并且所有 bag 中的元素都会调用 dispose() 方法
    let disposeBag = DisposeBag()
    let model = GoogleModel()
    
    @IBOutlet weak var googleText: UITextView!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // 记住使用 [weak self] 或者 [unowned self] 来避免循环引用
        model.createGoogleDataObservable()
            .subscribe(onNext: { [weak self] (element) in
                self?.googleText.text = element
            }).addDisposableTo(disposeBag)
    }
}

神奇吗?没有协议, 没有代理。只是声明了一下在一个事情发生的时候应该做什么。

在闭包中记得使用 [weak self] 或者 [unowned self] 来避免循环引用

还有一种更响应式的方法来为 UITextView 绑定文本, 绑定。但那是更高级的内容。

Dispose Bag Example

你可能已经发现了 disposeBagViewController 的一个成员变量。

class ViewController: UIViewController {

let disposeBag = DisposeBag()

当这个控制器被释放的时候,它也会释放掉这个 disposeBag .

如果这个 disposeBag 被释放掉之后, 它我们添加到这个 bag 里面所有的信号都会被释放掉。而这个网络请求任务如果还没有结束的话也会被取消。
希望我讲清楚了 DisposeBag 的机制。

That‘s it!

Demo 我已经放在 GitHub 上了。

到现在, 我们已经学到了如何创建一个 Observable 和 订阅者。以及 disposing 机制是怎么回事。希望你能够理解到这样做比平常的观察者模式有什么优势。

下一篇是关于 RxSwift 操作符的。

原文地址

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

推荐阅读更多精彩内容