RxSwift-理解create和debug operator

有时候,Observable中的事件值并不像整数或者字符串这么简单,当我们需要精确控制发送给订阅者的成功、错误和结束事件时,就可以使用RxSwift提供的create operator。

创建自定义事件序列

在Observable+Creation.swift里,可以看到create的签名是这样的:

public static func create(
    _ subscribe: @escaping (AnyObserver<E>) -> Disposable
) -> Observable<E>

单看这堆复杂又相似的名字,就会觉得这个函数不太好理解,create究竟是如何自定义的方式创建Observable的呢?理解这个问题,我们得从它的参数入手。
这里,subscribe并不是指事件真正的订阅者,而是用来定义当有人订阅Observable中的事件时,应该如何向订阅者发送不同情况的事件,理解这个问题,是使用create的关键。
于是,create调用大体的结构,就是这样的:

let customOb = Observable<Int>.create {
    // Hanle next, error, completed event here
}

然后,再来看subcribe自身,当然,它是一个closure,此时,AnyObserver<E>在这个closure里,表示任意一个订阅的“替身”,我们要用这个“替身”来表达向订阅者发送各种事件的行为。理解了这个概念,我们的create调用就可以进一步细化成这样:

let customOb = Observable<Int>.create { observer in
    // next event
    observer.onNext(10)

    observer.onNext(11)

    // complete event
    observer.onCompleted()
}

表示,只要有人订阅了customOb中的事件,我们就先向订阅者发送两次.next事件,值分别是10和11,然后,发送.completed表示Observable结束。
最后,subscribe还要返回一个Disposable对象,我们可以用这个对象取消订阅进而回收customOb占用的资源:

let customOb = Observable<Int>.create { observer in
    // The same as above...

    return Disposables.create()
}

这样,我们就自定义了一个Observable,它会向每个订阅者发送10和11,然后结束。我们可以用下面的代码来试一下:

let disposeBag = DisposeBag()

customOb.subscribe(
    onNext: { print($0) },
    onCompleted: { print("Completed") },
    onDisposed: { print("Game over") }
).addDisposableTo(disposeBag)

// 10
// 11
// Completed
// Game over

就可以再控制台看到上面注释中的结果了。至此,我们就完成了90%的工作了,但你可能还记得,我们还没自定义发生错误时,向订阅者发送的内容。其实很简单,先定义一个表示具体错误的类型:

enum CustomError: Error {
    case somethingWrong
}

然后,在create的定义里,要发生错误的地方,直接通知订阅者就好了:

let customOb = Observable<Int>.create { observer in
    // next event
    observer.onNext(10)

    observer.onError(MyError.somethingWrong)

    observer.onNext(11)

    // complete event
    observer.onCompleted()

    return Disposables.create()
}

最后,在订阅的时候,我们可以直接通过onError来得到错误通知:

customOb.subscribe(
    onNext: { print($0) },
    onError: { print($0) },
    onCompleted: { print("Completed") },
    onDisposed: { print("Game over") }
).addDisposableTo(disposeBag)

// 10
// somethingWrong
// Game over

重新执行之前订阅,我们就只能在结果中看到上面注释的结果了。

事件序列调试

在实际的编程中,有时我们会串联多个operator对事件序列进行处理,虽然这样写起来很方便,当发生问题调试时候就很麻烦了,因为紧密的串联在一起的代码让我们很难方便的洞察每一个环节状态。为此,RxSwift提供了一个类似“旁路”功能的operator:do。它的用法和subscribe类似,只不过事件会“穿过”do,继续发给后续的环节。这样,如果我们怀疑某个串联的环节发生了问题,就可以插入一个do operator进行观察:

customOb.do(
    onNext: { print("Intercepted: \($0)") },
    onError: { print("Intercepted: \($0)") },
    onCompleted: { print("Intercepted: Completed") },
    onDispose: { print("Intercepted: Game over") }
)
.subscribe(
    onNext: { print($0) },
    onError: { print($0) },
    onCompleted: { print("Completed") },
    onDisposed: { print("Game over") }
).addDisposableTo(disposeBag)

这样,customOb中的事件就会“流经”do之后,继续发送给subscribe,方便我们观察订阅到的每一个内容。重新执行一下,就能看到下面这样的结果:

// Intercepted: 10
// 10
// Intercepted: somethingWrong
// somethingWrong
// Game over
// Intercepted: Game over

此时,如果你足够眼尖,可能已经发现上面例子中的一个小细节了。在do里,用于处理取消订阅事件参数是onDispose,但subscribe中对应的则是onDisposed,甚至,这个微小的差异还影响了最终打印的结果。
看到这里,你可能会想,用do进行调试并不方便,毕竟还要写一堆的on,再配上各自的closure,应该有一个专门可以穿插在各种operator之间进行调试的operator。实际上,do也的确不是为了调试而生的,我们只是借用了它的“旁路”特性而已。RxSwift提供了一个调试专属的operator,叫做debug,它可以安插在任意一个需要确认事件值的地方,像这样:

customOb.debug()
.subscribe(
    onNext: { print($0) },
    onError: { print($0) },
    onCompleted: { print("Completed") },
    onDisposed: { print("Game over") }
).addDisposableTo(disposeBag)

在上面的例子里,我们把debug安插在了订阅前取代了do operator。这样,debug就会在不影响subscribe的同事,自动打印customOb发出的所有事件。执行一下,就可以得到类似下面的结果:

2017-04-06 18:56:25.348: main.swift:23 (RxSwiftInSPM) -> subscribed
2017-04-06 18:56:25.355: main.swift:23 (RxSwiftInSPM) -> Event next(10)
10
2017-04-06 18:56:25.356: main.swift:23 (RxSwiftInSPM) -> Event error(somethingWrong)
somethingWrong
Game over
2017-04-06 18:56:25.356: main.swift:23 (RxSwiftInSPM) -> isDisposed

可以看到,带有时间的源代码信息的行,就是debug operator提供的详细信息,包括了从订阅开始,收到.next,收到.error一直到最后.disposed的全过程,这样,调试起来,就方便多了。

What's next?

至此,可以说,你已经步入RxSwift的大门了。但你可能还有一个感觉:尽管一直以来我们在不断强调着Observable表达的异步概念,但使用的例子总让我们没有异步的感觉。例如,一连串随机点击的按钮事件、或者处理一个网络请求才是异步的,像这样提前设计好一个Observable钟的所有值,然后发送出来怎么能叫异步的呢?

实际上,这些带给我们的异步感觉的操作,都需要在运行时动态给Observable添加内容。这也就意味着,这种Observable要有双重身份:一方面,它自身得是一个订阅者以获得系统事件的通知;另一方面,它也得是一个Observable,供我们编写的客户端代码进行订阅。在Rx的世界里,这也具有双重身份的对象,有一个专属的名字,叫做Subject。因此,在用更真实的方式学习RxSwift之前,我们还需要额外做些工作。

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

推荐阅读更多精彩内容

  • 发现 关注 消息 RxSwift入坑解读-你所需要知道的各种概念 沸沸腾关注 2016.11.27 19:11*字...
    枫叶1234阅读 2,792评论 0 2
  • 本文章内部分图片资源来自RayWenderlich.com 本文结合自己的理解来总结介绍一下RxSwift最基本的...
    FKSky阅读 2,873评论 4 14
  • 在正文开始之前的最后,放上GitHub链接和引入依赖的gradle代码: Github: https://gith...
    苏苏说zz阅读 677评论 0 2
  • 转一篇文章 原地址:http://gank.io/post/560e15be2dca930e00da1083 前言...
    jack_hong阅读 912评论 0 2
  • 开始分享到现在第12天,我为自己的坚持感到高兴。每天打开手机第一件事就是打开学习群,挨着看大家的分享,群中大家分享...
    四叶草hr阅读 207评论 0 0