前言

看完本系列前面几篇之后,估计大家也还是有点懵逼,本系列前八篇也都是参考RxSwift官方文档和一些概念做的解读。上几篇文章概念性的东西有点多,一时也是很难全部记住,大家脑子里面知道有这么个概念就行,用的时候,再来查阅一番,慢慢就掌握了。

本篇主要来深入了解一些RxSwift实战中用到的一些重要知识点,这里面有很多自己的理解,所以不免会有一些错误的地方,还请大家多多交流,如有发现错误的地方,欢迎评论。

概念

Rx系列的核心就是Observable Sequence这个相信大家心中已经有所了解了,这里不再啰嗦了,建议大家看看前面几篇文章去了解一下。接下来介绍一些容易混淆和难以理解的概念。

Observable 和 Observer

相信大家看前面几篇文章的时候,大量出现这两个东西,为了理解这两个东西,我们先来简单介绍下观察者模式吧。

比如一个宝宝在睡觉,爸爸妈妈不可能时时刻刻待在那看着吧?那样子太累
了。他们该做啥做啥,只要听到宝宝哭声的时候,他们给宝宝喂奶就行了。这就是一个简单的观察者模式。宝宝是被观察者,爸爸妈妈是观察者也称作订阅者,只要被观察者发出了某一个事件,比如宝宝哭声,叫声都是一个事件,订阅者就会做出相应地响应。

理解了观察者模式这两个概念就很好理解了,Observable就是可被观察的,也就是我们说的宝宝,他也是事件源。而Observer就是我们的观察者,也就是当收到事件的时候去做某些处理的爸爸妈妈。观察者需要去订阅(subscribe)被观察者,才能收到Observable的事件通知消息。

subscribe 和 subscribe(onNext:)

subscribe是订阅sequence发出的事件,比如next事件,error事件等。而subscribe(onNext:)是监听sequence发出的next事件中的element进行处理,他会忽略errorcompleted事件。相对应的还有subscribe(onError:) 和 subscribe(onCompleted:)

Dispose 和 DisposeBag

当监听一个sequence的时候,有消息事件来了,我们做某些事情。但是这个sequence不再发送消息事件了,那么我们的监听也就没有什么存在的价值了,所以我们需要释放我们这些监听资源,其实也就是内存资源释放。
释放某一个监听的时候,我们有两种方式处理:

我们可以手动调用释放方式,但是我们一般不适用这种方式。

// 关于scheduler,我们会在下面讲到let subscription = Observable<Int>.interval(0.3, scheduler: SerialDispatchQueueScheduler.init(internalSerialQueueName: "scott"))
    .observeOn(MainScheduler.instance)  //observeOn也会在下面讲到
    .subscribe { event in
        print(event)
}    
Thread.sleep(forTimeInterval: 2.0)
    
subscription.dispose()

打印结果:

next(0)next(1)next(2)next(3)next(4)next(5)

比如上面这个例子,我们创建了一个subscription监听,在两秒以后我们不需要了,手动调用dispose()方法,就能释放监听资源,不再打印信息。上面的subscription不论是在哪个线程中监听,就算在主线程中调用的dispose()方法一样会销毁资源。

除了上述手动释放资源外,还有一种自动方式,推荐大家使用这种方式,这种方式就像iOS中的ARC,会在适当的时候销毁观察者,自动释放资源。

let disposeBag = DisposeBag()
   
Observable<Int>.empty()
   .subscribe { event in
       print(event)
   }
   .addDisposableTo(disposeBag)

如上个例子,我们创建一个disposeBag来盛放我们需要管理的资源,然后把新建的监听都放进去,会在适当的时候销毁这些资源。如果你需要立即释放资源只需要新建一个DisposeBag(),那么上一个DisposeBag就会被销毁。

observeOn() 和 subscribeOn()

这两个东西刚开始看的时候也是一脸懵逼,就知道最好多用observeOn(),但是不知道为什么,下面我们就来揭开它们的面纱看下它们的真面目吧。

区别其实我感觉就一句话,subscribeOn()设置起点在哪个线程,observeOn()设置了后续工作在哪个线程。例如:

someObservable 
    .doOneThing()  // 1
    .observeOn(MainRouteScheduler.instance) // 2
    .subscribeOn(OtherScheduler.instance) // 3
    .subscribeNext { // 4
        ......
    }
    .addDisposableTo(disposeBag)
  1. 所有动作都发生在当前的默认线程

  2. observeOn()转换线程到主线程,下面所有的操作都在主线程

  3. subscribeOn()规定动作一开始不是发生在默认线程,而是在OtherScheduler了。

  4. 如果我们之前没有调用observeOn(),那么这边会在OtherScheduler发生,但是我们前面调用了observeOn(),所以这个动作会在主线程中调用。

总结一下:subscribeOn()只是影响事件链开始默认的线程,而observeOn()规定了下一步动作发生在哪个线程中。

shareReplay

看官方项目里面的Demo时,我也很疑惑,为什么很多的sequence后面都有shareReplay(1)呢?想的昏头涨脑。
这里我就给大家讲解一下我的理解吧。先看一个例子:

let disposeBag = DisposeBag()   
let observable = Observable.just("??").map{print($0)}
    
observable.subscribe{print("Even:\($0)")}.disposed(by: disposeBag)
observable.subscribe{print("Even:\($0)")}.disposed(by: disposeBag)

打印结果:

??Even:next(())Even:completed??Even:next(())Even:completed

大家发现没有,map()函数执行了两次,但是有些时候,我们并不想让map()函数里面的东西执行两次,比如map()函数里面执行的是网络请求,我只需要执行一次请求,然后把结果提供给大家使用就行了,多余的请求会增加负担。所以这时候就需要使用shareReplay(1)了。这里面的数字一般是1,只执行一次。(ps:我改成 2,3 也只打印一次)

let disposeBag = DisposeBag()   
let observable = Observable.just("??").map{print($0)}.shareReplay(1)
    
observable.subscribe{print("Even:\($0)")}.disposed(by: disposeBag)
observable.subscribe{print("Even:\($0)")}.disposed(by: disposeBag)

打印结果:

??Even:next(())Even:completedEven:next(())Even:completed

自定义operator

自定义操作符很简单,官方推荐尽量使用标准的操作符,但是你也可以定义自己的操作符,文档上说有两种方法,这里介绍一下常用的一种方法吧。

例如我们自定义一个map操作符:

extension ObserverType {    func myMap<R>(transform: E -> R) -> Observable<R> {        return Observable.create{ observer in
            let subscription = self.subscribe {e in
                switch e{                case .next(let value):                    let result = transform(value)
                    observer.on(.next(result))                case .error(let error):
                    observer.on(.error(error))                case .completed:
                    observer.on(.completed)
                }
            }            return subscription
        }
    }
}

参数是一个闭包,其中闭包参数是E类型返回值是R类型,map函数的返回值是一个Observable类型。

Driver

这又是啥东东? 讲解Driver之前我们现在看看下面的??:

let results = query.rx.text
    .throttle(0.3, scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
    }
    
results
    .map { "\($0.count)" }
    .bindTo(resultCount.rx.text)
    .addDisposableTo(disposeBag)
    
results
    .bindTo(resultsTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .addDisposableTo(disposeBag)
  • 首先创建一个可监听序列results,其中flatMapLatest下面会讲;

  • 然后将results绑定到resultCount.rx.text上;

  • results绑定到resultsTableView上.

上面程序会出现下面几个异常:

  1. 如果fetchAutoCompleteItems出错,那么它绑定的UI将不再收到任何事件消息;

  2. 如果fetchAutoCompleteItems发生在后台线程,那么它绑定的事件也将在后台线程执行,这样更新UI会造成crash

  3. 有两次绑定,fetchAutoCompleteItems就会执行两次

当然针对以上问题,我们也有解决方案,针对第三点,我们可以使用神器shareReplay(1)保证只执行一次,可以使用observeOn()保证后面所有操作在主线程完成。

let results = query.rx.text
    .throttle(0.3, scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
            .observeOn(MainScheduler.instance)
            .catchErrorJustReturn([])           
    }
    .shareReplay(1)
                                 
results
    .map { "\($0.count)" }
    .bindTo(resultCount.rx.text)
    .addDisposableTo(disposeBag)
    
results
    .bindTo(resultTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .addDisposableTo(disposeBag)

我们也可以使用Driver来解决:

let results = query.rx.text.asDriver()
    .throttle(0.3, scheduler: MainScheduler.instance)
    .flatMapLatest { query in
        fetchAutoCompleteItems(query)
            .asDriver(onErrorJustReturn: [])  //当遇见错误需要返回什么
    }   //不需要添加shareReplay(1)
    results
    .map { "\($0.count)" }
    .drive(resultCount.rx.text)     //和bingTo()功能一样
    .addDisposableTo(disposeBag) 
                                             
results
    .drive(resultTableView.rx.items(cellIdentifier: "Cell")) { (_, result, cell) in
        cell.textLabel?.text = "\(result)"
    }
    .addDisposableTo(disposeBag)

drive方法只能在Driver序列中使用,Driver有以下特点:

  1. Driver序列不允许发出error

  2. Driver序列的监听只会在主线程中。

所以Driver是专为UI绑定量身打造的东西。
以下情况你可以使用Driver替换BindTo:

  • 不能发出error;

  • 在主线程中监听;

  • 共享事件流;

map 和 flatMap 何时使用

看了前面《RxSwift 系列(四) -- Transforming Operators》,我想大家对于何时使用mapflatMap也还是有疑惑。

我们来看看map函数和flatMap函数的定义:
map函数,接收一个R类型的序列,返回一个R类型的序列,还是原来的序列。

public func map<R>(_ transform: @escaping (Self.E) throws -> R) -> RxSwift.Observable<R>

flatMap函数,接收一个O类型的序列,返回一个O.E类型的序列,也就是有原来序列里元素组成的新序列。

public func flatMap<O: ObservableConvertibleType>(_ selector: @escaping (E) throws -> O)
        -> Observable<O.E>

其实这里的mapflatMapswift中的作用是一样的。map函数可以对原有序列里面的事件元素进行改造,返回的还是原来的序列。而flatMap对原有序列中的元素进行改造和处理,每一个元素返回一个新的sequence,然后把每一个元素对应的sequence合并为一个新的sequence序列。

看下面例子:

let disposeBag = DisposeBag()   
let observable = Observable.of("1","2","3","4","5").map{$0 + "scott"}
    
observable.subscribe(onNext: {print($0)}).disposed(by: disposeBag)

打印结果:

1scott
2scott
3scott
4scott
5scott

我们使用map对序列中每一个元素进行了处理,返回的是一个元素,而使用flatMap需要返回的序列。那么使用map也返回一个序列看看。

let test = Observable.of("1", "2", "3", "4", "5")
    .map { Observable.just($0) }
    
test.subscribe(onNext: {        print($0)
    })
    .addDisposableTo(disposeBag)

运行结果:

RxSwift.(Just in _BD9B9D4356C4038796FB16D0D54A9F8E)<Swift.String>
RxSwift.(Just in _BD9B9D4356C4038796FB16D0D54A9F8E)<Swift.String>
RxSwift.(Just in _BD9B9D4356C4038796FB16D0D54A9F8E)<Swift.String>
RxSwift.(Just in _BD9B9D4356C4038796FB16D0D54A9F8E)<Swift.String>
RxSwift.(Just in _BD9B9D4356C4038796FB16D0D54A9F8E)<Swift.String>

看到结果会打印出每一个序列,下面我们使用merge()方法将这几个序列进行合并:

let test = Observable.of("1", "2", "3", "4", "5")
    .map { Observable.just($0) }.merge()
    
test.subscribe(onNext: {        print($0)
    })
    .addDisposableTo(disposeBag)

运行结果:

1
2
3
4
5

合并为一个新序列后我们就可以正常打印元素了。下面看看使用faltMap()函数干这件事:

let test = Observable.of("1", "2", "3", "4", "5")
    .flatMap { Observable.just($0) }
    
test.subscribe(onNext: {        print($0)
    })
    .addDisposableTo(disposeBag)

运行结果:

1
2
3
4
5

看下对比是不是一样,这样子对比就清晰了吧。

  • map函数只能返回原来的那一个序列,里面的参数的返回值被当做原来序列中所对应的元素。

  • flatMap函数返回的是一个新的序列,将原来元素进行了处理,返回这些处理后的元素组成的新序列

  • map函数 + 合并函数 = flatMap函数

flatMap函数在实际应用中有很多地方需要用到,比如网络请求,网络请求可能会发生错误,我们需要对这个请求过程进行监听,然后处理错误。只要继续他返回的是一个新的序列。

UIBindingObserver

UIBindingObserver这个东西很有用的,创建我们自己的监听者,有时候RxCocoa(RxSwift中对UIKit的一个扩展库)给的扩展不够我们使用,比如一个UITextField有个isEnabled属性,我想把这个isEnabled变为一个observer,我们可以这样做:

extension Reactive where Base: UITextField {
    var inputEnabled: UIBindingObserver<Base, Result> {        return UIBindingObserver(UIElement: base) { textFiled, result in
            textFiled.isEnabled = result.isValid
        }
    }
}

UIBindingObserver是一个类,他的初始化方法中,有两个参数,第一个参数是一个元素本身,第一个参数是一个闭包,闭包参数是元素本身,还有他的一个属性。

public init(UIElement: UIElementType, binding: @escaping (UIElementType, Value) -> Swift.Void)

自定义了一个inputEnabled关联了UITextFieldisEnabled属性。

致谢

本系列文章理论性的东西就算是讲述完了,如果你发现有错误,欢迎交流,共同进步,谢谢。接下来准备写点实战性的,大家准备好!

http://www.cnblogs.com/scott-mr/p/7234769.html

延伸阅读

告别“老顽固”-Java培训,做最负责任的教育,学习改变命运,软件学习,再就业,大学生如何就业,帮大学生找到好工作,lphotoshop培训,电脑培训,电脑维修培训,移动软件开发培训,网站设计培训,网站建设培训告别“老顽固”