1-RxSwift中基本概念和使用

RxSwit中最重要的一个概念叫做Observable,我们可以把他翻译为序列、可观察序列等等

流、序列、可观察序列,都是一样的东西。就是一个可以持续发出事件的东西。

在Rx中 方法也被称作为 操作符

怎样创建一个信号流

  • just 方法

    let observable = Observable.just(1)

    just是Observab了的一个静态方法

    用这个方法创建了一个只发射一个元素的信号流

  • of 方法

    Observable.of(1, 2, 3, 4)

    just方法只能接受一个元素,如果有多个元素需要用过信号流发送出去,就可以使用 of 方法

    使用场景:如果要对一个数组进行遍历 就可以通过 of 方法来代替 for in 循环

  • from 方法

    Observable.from([1, 2, 3, 4])

    from方法也是创建了一个信号流,但是它接受的是一个数组,然后把这个数组中的元素逐个发送出去

    使用场景:如果传过来的就是一个数组,而不是一个一个元素,要进行遍历的时候就可以使用 from 方法生成的信号流

怎样监听一个信号流

在没有RxSwift 我们很熟悉 NotificationCenter,在通知模式中,我们想要监听一个通知会通过addObserver()监听指定的通知。

在RxSwift中和上面很相似,只不过把addObserver()替换成了subscribe()

但是和通知不一样的是,通知我们一般使用的是它的单例,而RxSwift的每一个信号源都是单独的。

如果一个信号没有被订阅的时候,它是不会发送事件的,内部的操作也不会执行的。

监听操作就像是在信号源和观察者之间建立一条通道,监听之后,才会有这条通道,信号源才会通过通道发送事件过来。

这个事件发送就像是一个流。我们可以处理这个事件流中的每一个事件。

通过 subscribe() 方法就可以监听一个信号流,下面是一个示例


    let observable = Observable.of(1, 2, 3)

    observable.subscribe{ event in 

        print(event)

    }

上面的代码会输出


    next(1)

    next(2)

    next(3)

    completed

点击subscribe方法可以看到,它的闭包的参数是一个 Event<Int> 类型的值,每次信号流发送信号,都把值放到Next事件包起来然后发送出来

但是通常我们使用的都是内部包起来的值 ,所以一般可以通过下面方式进行使用


    observable.subscribe { event in 

        if let value = event.element {

            print(value)

        }

    } 

Event 有一个可选性的 element属性,因为只有 Next 事件才会有element,所以可以通过解包来拿到内部的值

因为事件的订阅是经常使用的,每次进行解包有些麻烦,所以Rx提供了一个快捷方法,对于每种事件都有一个操作符进行监听

    
    observable.subscribe {

        onNext: { element in 

            print(element)

        }, 

        onCompleted: {

            print("completed!")

        }

    }

信号流的终止和释放

再提一次:一个信号源如果没有观察者监听,它不会做任何事。

是这个监听的动作触发了信号源的工作,让它一直发送事件,一直到发送了一个error或completed事件才会终止这个信号(信号源和观察者之间就断开了通道,后续的事件就不会受到了)。

除了通过发送 error 或 completed 事件,我们也可以通过取消监听来手动来销毁这个信号源,从而终止事件流。

写一个代码看一下信号流的终止


    let observable = Observable.of("A", "B", "C")

    let subscription = observable.subscribe { event in 

        print(event)

    }

代码解释

  1. 创建了一个字符串的信号流

  2. 监听了这个信号。同时,将这个信号源的返回值Disposable 用一个常量subscription保存了起来

  3. 在闭包里面打印每一个事件

上面的代码并没有将这个信号源进行释放。也就是说这个信号流的所有信号都发送完了,但是在监听者和信号源之间的通道还存在。所以这里是没有意义的。它也会带来内存泄漏的问题。

如果要主动的取消订阅,可以让 销毁器(Disposable类型的subscription) 通过调用 dispose() 方法。当释放了这个信号源之后,它就不会再发送任何事件了。


subscription.dispose()

单独的处理每一个信号的订阅的销毁太麻烦,所以RxSwift提供了一个 DisposeBag 类型

一个DisposeBag 包含了所有的需要等待释放的 disposable

通常通过 disposed(by:)方法将订阅者添加到 这个DisposeBag包中,这个包在释放的时候,会对包中的每一个 订阅者(disposable) 调用 dispose() 方法进行终止信号流的操作。

所以一个通常的模式就是这样

    let disposeBag = DisposeBag()

    Observable.of("A", "B", "C").subscribe{ 

        print($0)

    }.disposed(by: disposeBag)



注释

  1. 先创建一个释放者包

  2. 创建一个信号流

  3. 监听这个信号流,并打印这发送的事件通过默认参数$0

  4. 将返回的disposable添加到释放包里面去

问题来了:这个销毁器是哪里来的?为什么通过subscribe()方法建立了监听之后就可以得到?dipose()方法执行了什么?。

这就要从信号最原始的创建方法 create() 来看

create 方法

之前看过提供的几个创建信号源的方法,都是自动就把事件都发出去了,我们没有主动的控制事件的发送。通过 create 方法就可以手动的发送所有可以使用的事件出去。

create的一个简单示例就是下面这样:


    Observable<String>.create { observer in 

        observer.onNext("1")

        observer.onCompleted()

        return Disposables.create()

    }

create方法的参数是一个闭包,闭包的形参名是 subscribe。那这个闭包什么时候执行。

从create的方法的代码注释中可以看到,这个闭包是 subscribe() 方法的实现。

所以就是说,它是在subscribe()方法执行的会被执行。

也就解释了为什么 一个信号如果没有产生订阅,它是不会执行任何操作的。

接下来 就要看一下这个闭包的参数和返回值

闭包接收一个AnyObserver参数,返回一个Disposable

我们上面介绍信号流的终止的时候看到了 subscribe() 的返回值是一个Disposable ,就是这里创建并返回的这个 Disposable。

AnyObserver 是一个泛型,它的作用就是把将要发送给订阅者的 值 添加到这个信号流中。

onNext() 是 on(.next(_:)) 的一个快捷方式

最后返回一个disposable 表示在这个信号流终止或销毁的时候要做的事情,在这个例子里面没有要清理的东西,所以返回了一个空的disposable

我理解这个 Disposable 和 Observable observer是没有什么关系的。它要做的是一些额外的资源的清理。

如果不发送 completed 或 error 这个信号流是不会停止的,也就不会执行 disposable 的,这个信号就不会被释放。(就是观察者和信号源之间的通道不会断开)

还有哪些方法可以创建信号流

  • empty 方法

    
        let observable = Observable<Void>.empty() 
    
        observable.subscribe {
    
            onNext: { element in 
    
                print(element)
    
            }, 
    
            onCompleted: {
    
                print("completed!")
    
            }
    
        }
    
    

    上面的示例会直接输出 completed!

    Observable.empty()方法会创建一个空的信号流,它会直接发送complete事件

    什么时候会使用 empty()方法创建信号流 :当需要通过一个信号来告诉某个事情完成的时候可以考虑通过empty()创建一个空信号

  • range 方法

        Observable.range(start: 1, count: 10).subscrbe(
    
            onNext: { value in 
    
                print(value)
    
            }
    
        )
    

    range方法会创建一个信号流,这个信号流会从start的值开始,发送信号,每次的信号值是上一次的值加1

    当需要快速生成一个序列的时候,可以考虑使用 range 方法

  • never 方法

    never创建出来的信号源,不会发送任何的事件。当监听之后,就会在监听者和信号之间建立一条通道,但是不会有任何的事件发送。

    具体的使用场景还没找到。

特殊信号

和普通的信号流相比,这些特殊信号的可操作能力更小。通常他们只有一种能力可以执行。

在任何使用特殊信号的地方都可以用普通信号代替。

这些特殊信号就是为了简化代码

在RxSwift中先介绍三种特殊信号: SingleMaybeCompletable

  • Single

    Single只会发出 success(value) 或 error(error) 事件。

    success(value)实际上是 next 和 completed的组合事件。这个信号的特点是会将最终的状态和结果值都发送出来

    使用场景:比如对一次数据处理想要得到最终的处理状态和结果(比如数据下载,最终需要的状态和下载完成的结果、或者比如从硬盘读取一个文件,我们最终需要的是读取的结果和读取的文件内容)。就可以使用Single

    
        // 定义文件读取错误时的错误类型

        enum FileReadError: Error {

            case fileNotFound, unreadable, encodingFailded

        }



        // 定义一个文件读取的方法 

        func loadDiskFile(path: String) -> Single<String> {

            return Single<String>.create {

                let disposable = Disposables.create()

                guard let path = Bundle.main.path(forResource: path, ofType: "txt") else {

                    single(.error(FileReadError.fileNotFound))

                    return disposable

                }

            
                guard let data = FileManager.default.contents(atPath: path) else {

                    single(.error(FileReadError.unreadable))

                    return disposable

                }


                guard let contents = String(data: data, encoding: .utf8) else {

                    single(.error(FileReadError.encodingFailed))

                    return disposable

                }


                single(.success(contents))

                return disposable

            }

        }


        // 通过Single获取结果 

        loadDiskFile("sample").subscribe(

            switch $0 {

                case .success(let content): 

                    print(content)

                case .error(let error): 

                    print(error)

            }

        ).disposed(by: DisposeBag()) 

  • Completable

    Completable 只会发送一个 completed 或 error(error)事件

    它发送的这个事件不会携带值出来

    如果只关心操作的结果,就可以考虑使用 Completable 信号

    使用场景:向硬盘中写一份数据操作,我们只需要关心数据有没有写入成功。这个操作完成不需要向我们返回什么数据,就使用使用Completable

  • Mayby

    Maybe是Completable和Single组合起来的一个操作符,它既可以像Single一样发送结果的状态和结果值 success(value), error(error),也可以只发送结果状态 completable

  • debug 操作符

    在调试Rx代码的时候想看看代码执行的bug,可以使用 debug 操作符

    通过这个操作符就可以打印每个事件的信息

    这个方法有几个可选的参数,可能最有用的是可以让我们添加一个唯一字符串在输出的时候可以一起输出出来

后面还会有更多的特殊信号

上面一部分的小总结

到这里,我们在上面介绍的什么是信号流、怎么创建信号,怎么监听信号和怎么在信号释放的做一些事情。

但是上面的这些信号本质是只读的,我们只能监听这些信号,然后得到这些信号产生的事件。(或者说这些信号更像是一次性的,它处理完一些事情后,就把信号发送出来,然后这个信号的作用就结束了。)

在开发的过程中可能我们更通用的需求是:在运行的时候手动的添加一个信号到信号流中发送给监听者。

这就是下面要介绍的 Subject

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

推荐阅读更多精彩内容