RxSwift-step by step 1-10

写在前面

这一系列笔记用于对泊学网RxSwift-step by step课程进行总结和摘选, 仅用于个人学习记忆.

笔记

01-异步事件是以时间为索引的常量队列

以时间为索引的常量队列的方式 这也是引入RxSwift的目的

02-安装RxSwift的三种不同的方式

学习使用playground来编写文档

安装方式

1.mac环境下使用cocoapods安装

2.非mac环境下可以使用 SPM 安装

在编辑SPM自动生成的package.swift文件, 修改 

然后退出编辑 执行swift build (会自动下载RxSwift 然后 自动执行build)

3.手动添加

将RxSwift(只是一个project)下载到当前目录下(这个指令值得深究)

03-RxSwift中的那些"术语"到底在说什么?

我们创建的Observable, 表达的是异步操作, Observable中的每一个元素, 都可以理解为一个异步发生的事件.因此,当我们对Observable调用map和filter方法时,只表示我们要对事件序列中的元素进行处理的逻辑,而并不会立即对Observable中的元素进行处理.

operator对Observable的加工是在订阅的时候发生的.这种只有在订阅的时候才emit事件的Observable,有一个专有的名字,叫做Cold Observable.

言外之意,就是也有一种Observable是只要创建了就会自动emit事件的,它们叫做Hot Observable.在后面的内容中,我们会看到这类事件队列的用法.

对于有限观察序列, 执行到头时 资源就自动释放,  但是有时有些观察序列并非有限 如 timer序列. 所以就需要我们对其进行手动释放,

当然, RxSwfit也提供另一种方式, DisposeBag实例.

调用disposed(by: ) 将Observable与 一个 DisposeBag绑定起来. 然后当这个bag实例销毁时, 所有其绑定的订阅者都将取消订阅 并 回收资源.

04 理解create和debug operator

理解 create函数中的subcribe参数的意义: 并非订阅, 而是 描述如何向订阅者发送订阅的消息

订阅

操作 do 旁路特性

debug(). 为Observable执行debug()操作, 会将所以订阅到的消息打印出来,

05 四种Subject的基本用法

上节末尾,我们提到了Subject.既然它可以同时作为ObservableObserver,我们就直奔主题,从一个叫做PublishSubject的对象开始,感受下Subject的用法:

PublishSubject

let subject = PublishSubject<String>()

subject.onNext("Episode1 updated")
// 第三,当我们把subject当作Observable的时候,订阅它的代码和订阅普通的Observable完全一样:
let sub1 = subject.subscribe(onNext: {
    print("Sub1 - what happened: \($0)")
})

BehaviorSubject

如果你希望Subject从会员制变成试用制,就需要使用BehaviorSubject.

BehaviorSubject

ReplaySubject

ReplaySubject的行为和BehaviorSubject类似,都会给订阅者发送历史消息.不同地方有两点:

  • ReplaySubject没有默认消息,订阅空的ReplaySubject不会收到任何消息;
  • ReplaySubject自带一个缓冲区,当有订阅者订阅的时候,它会向订阅者发送缓冲区内的所有消息;
// ReplaySubject缓冲区的大小,是在创建的时候确定的:
let subject = ReplaySubject<String>.create(bufferSize: 2)

Variable

除了事件序列之外,在平时的编程中我们还经常需遇到一类场景,就是需要某个值是有"响应式"特性的.为了方便这个操作,RxSwift还提供了一个特殊的subject,叫做Variable.

// 我们可以像定义一个普通变量一样定义一个Variable:
let stringVariable = Variable("Episode1")

// 当我们要订阅一个Variable对象的时候,要先明确使用asObservable()方法.而不像其他subject一样直接订阅:
let stringVariable = Variable("Episode1")

let sub1 = stringVariable
    .asObservable()
    .subscribe {
    print("sub1: \($0)")
}

// 而当我们要给一个Variable设置新值的时候,要明确访问它的value属性,而不是使用onNext方法:
stringVariable.value = "Episode2"

最后要说明的一点是,Variable只用来表达一个"响应式"值的语义,因此,它有以下两点性质:

  • 绝不会发生.error事件;
  • 无需手动给它发送.complete事件表示完成;

07 TodoDemo2

资源被正常回收了么?

此时,尽管已经可以正常添加Todo了,但是如果你足够细心就可以发现,控制台并没有打印Finsih adding a new todo.的提示.也就是说,在dismiss了TodoDetailViewController之后,todoSubject并没有释放,我们应该在某些地方导致了资源泄漏.

为了进一步确认这个问题,在Podfile中添加下面的内容:

post_install do |installer|
  installer.pods_project.targets.each do |target|
    if target.name == 'RxSwift'
      target.build_configurations.each do |config|
        if config.name == 'Debug'
          config.build_settings['OTHER_SWIFT_FLAGS'] ||= ['-D', 'TRACE_RESOURCES']
        end
      end
    end
  end
end

简单来说,就是找到项目中的RxSwift target,在它的Debug配置中,添加-D TRACE_RESOURCES编译参数,并在Termianl中重新执行pod install更新下RxSwift.然后,在TodoDetailViewControllerviewWillAppear方法中,添加下面的代码:

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    todoName.becomeFirstResponder()

    todoItem = TodoItem()

    print("Resource tracing: \(RxSwift.Resources.total)")
}

但事情至此还没结束,可能你会觉得这样写代码感觉怪怪的,甚至有些危险.因为我们要依赖一个Controller(TodoDetailViewController)中的某个属性(bag)才能得以工作正常.而常规的开发经验通常告诉我们,如此密切的耦合关系通常是各种问题滋生的温床.这至多,只能算一个"非主流"的办法.

那么,更"主流"的办法是什么呢?

希望你还记得,对于一个Observable来说,除了所有订阅者都取消订阅会导致其被回收之外,Observable自然结束(onCompleted)或发生错误结束(onError)也会自动让所有订阅者取消订阅,并导致Observable占用的资源被回收.

因此,当TodoDetailViewController dismiss之后,实际上我们也不会再使用它添加新的Todo了,这时,我们应该给todoSubject发送onCompeleted事件,明确告知RxSwift,这个事件序列结束了.

08 ToDoDemo3

可以看到,是否同步成功是通过调用completionHandler通知的,仿照之前的思路,我们可以让syncTodoToCloud返回一个Observable<URL>,其中的URLiCloud保存在本地的路径:

func syncTodoToCloud() -> Observable<URL> {
    // ...

    return Observable.create({ observer in
        plist.save(to: cloudUrl, for: .forOverwriting,
            completionHandler: { (success: Bool) -> Void in

            if success {
                observer.onNext(cloudUrl)
                observer.onCompleted()
            } else {
                observer.onError(SaveTodoError.cannotCreateFileOnCloud)
            }
        })

        return Disposables.create()
    })
}

要特别强调的是:onCompleted对于自定义Observable非常重要,通常我们要在onNext之后,自动跟一个onCompleted,以确保Observable资源可以正确回收.

09 常用的忽略事件操作符

事例代码

example("ignoreElements") {
    let tasks = PublishSubject<String>()
    let bag = DisposeBag()

    tasks.subscribe { print($0) }
        .addDisposableTo(bag)

    tasks.onNext("T1");
    tasks.onNext("T2");
    tasks.onNext("T3");
    tasks.onCompleted();
}

/*
正常输出:
next(T1)
next(T2)
next(T3)
completed 
*/

Ignore elementss

ignoreElements 会忽略序列中所有的.next事件.

将事例代码中的订阅部分替换成以下:

tasks.ignoreElements()
    .subscribe { print($0) }
    .addDisposableTo(bag)

/*
输出:
completed
*/

用序列图描述, 就是这样的:
IgnoreElements

skip

skip可以用来忽略事件序列中特定个数的.next.

tasks.skip(2)
    .subscribe {
        print($0)
    }
    .addDisposableTo(bag)

/*
输出:
next(T3)
completed
*/

用序列图描述, 就是这样的:
Skip

skipWhile / skipUntil

skipWhile

除了可以忽略指定个数的事件外,我们还可以通过一个closure自定义忽略的条件,这个operator叫做skipWhile.但它和我们想象中有些不同的是,它不会"遍历"事件序列上的所有事件,而是当遇到第一个不满足条件的事件之后,就不再忽略任何事件了.

使用如下代码替换订阅部分代码:

tasks.skipWhile {
    $0 != "T2"
}
.subscribe {
    print($0)
}
.addDisposableTo(bag)

输出:

next(T2) // 注意还是会订阅到T2的事件
next(T3)
completed

skipwhile

skipUntil

另外一个和skipWhile类似的operatorskipUntil,它不用一个closure指定忽略的条件,而是使用另外一个事件序列中的事件.例如,我们先把代码改成这样:

let tasks = PublishSubject<String>()
let bossIsAngry = PublishSubject<Void>()
let bag = DisposeBag()

tasks.skipUntil(bossIsAngry)
    .subscribe {
        print($0)
    }
    .addDisposableTo(bag)

tasks.onNext("T1");
tasks.onNext("T2");
tasks.onNext("T3");
tasks.onCompleted();

执行一下就会看到,我们不会订阅到任何事件.这就是skipUntil的效果,它会一直忽略tasks中的事件,直到bossIsAngry中发生事件为止.

在T2与T3中插入如下代码

tasks.onNext("T1");
tasks.onNext("T2");
bossIsAngry.onNext();
tasks.onNext("T3");

输出:

next(T3)
completed

用序列图描述出来, 就是这样的:
skipUntil

distinctUntilChanged

distinctUntilChanged可以用来忽略序列中连续 重复 的事件. 例如:

example("ignoreElements") {
    let tasks = PublishSubject<String>()
    let bag = DisposeBag()

    tasks.distinctUntilChanged()
        .subscribe {
            print($0)
        }
        .addDisposableTo(bag)

    tasks.onNext("T1")
    tasks.onNext("T2")
    tasks.onNext("T2")
    tasks.onNext("T3")
    tasks.onNext("T3")
    tasks.onNext("T4")
    tasks.onCompleted()
}

由于T2和T3都存在连续重复的事件, 因此我们只能订阅到下面这样的结果

next(T1)
next(T2)
next(T3)
next(T4)
completed

它的序列图是这样的:
distinctUntilChangesd
但是,如果把T2放到两个T3中间,此时就没有任何连续重复的事件了,我们就会订阅到所有任务.

10 常用的获取事件操作符

elementAt

tasks.elementAt(1)
    .subscribe {
        print($0)
    }
    .addDisposableTo(bag)

/*
输出
next(T2)
completed
*/

filter

tasks.filter { $0 == "T2" }
    .subscribe {
        print($0)
    }
    .addDisposableTo(bag)

/* 
输出:
next(T2)
completed
*/

take

除了选择订阅单一事件之外,我们也可以选择一次性订阅多个事件,例如,选择序列中的前两个事件:

tasks.take(2)
    .subscribe {
        print($0)
    }
    .addDisposableTo(bag)

takeWhile/takeWhileWithIndex

我们也可以用一个closure来指定"只要条件为true就一直订阅下去"这样的概念.例如,只要任务不是T3就一直订阅下去:

tasks.takeWhile {
        $0 != "T3"
    }
    .subscribe {
        print($0)
    }
    .addDisposableTo(bag)

控制台输出:

next(T1)
next(T2)
completed

takeWhileWithIndexclosure有两个参数,第一个是事件的值,第二个是事件在序列中的索引。它的语义和takeWhile是完全一样的,需要注意的仍旧是,在closure里写的,是读取事件的条件,而不是终止读取的条件.

takeUntil

example("ignoreElements") {
    let tasks = PublishSubject<String>()
    let bossHasGone = PublishSubject<Void>()
    let bag = DisposeBag()

    tasks.subscribe.takeUntil(bossHasGone) {
            print($0)
        }
        .addDisposableTo(bag)

    tasks.onNext("T1")
    tasks.onNext("T2")
    tasks.onNext("T3")
    tasks.onCompleted()
}

tasks.onNext("T1")
tasks.onNext("T2")

bossHasGone.onNext()

tasks.onNext("T3")
tasks.onCompleted()

输出:

next(T1)
next(T2)
completed
订阅评论
提醒
guest
0 评论
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x