RxSwift第1部分

ReactiveX logo


早上好,哈布罗夫斯克。 在本系列文章中,我想谈谈反应式编程,即框架
RxSwift 。 在Habré和网络上有关于RxSwift的文章,但我认为,对于初学者来说,这些文章太难了。 因此,如果您开始了解iOS中的反应式编程,那么我会要求猫。


让我们从定义什么是反应式编程开始。


响应式编程是一种编程范例,专注于数据流和变更的传播。

那就是伟大的维基百科告诉我们的。


换句话说,当我们以命令式编程时,我们在代码中编写了必须顺序执行的一组命令。 反应式编程风格遵循其他几个概念。 通过反应式编程风格,我们的程序是观察对象中状态变化的“侦听器”。 听起来很复杂,但事实并非如此,仅此一个概念就足够了,一切将变得非常容易和清晰, 尚无错误


我不会画出如何安装框架,只需单击链接即可轻松完成。 让我们开始练习。


可观察的


让我们从简单但重要的可观察或可观察开始。 可观察的是将为我们提供数据的东西,它是生成数据流所必需的。


let observable = Observable<String>.just(" observable") 

宾果 ! 我们创建了第一个可观察的。


那又怎样


由于创建了观察对象,因此逻辑上需要创建一个将观察的对象。


 let observable = Observable<String>.just(" observable") _ = observable.subscribe { (event) in print(event) } 

我们在日志中得到以下内容:


 next( observable) completed 

完成了吗


Observable向我们发送有关其事件的信息,只有3种类型:


  • 下一个
  • 错误
  • 已完成

我们发送的元素以及我们发送的所有事件与下一个元素一起出现,在发生错误的情况下, 错误将按照名称的含义发送,在可观察对象发送所有数据并结束操作的情况下,错误将完成


我们可以创建更详细的 观察者 Subscriber'a,并获得更方便的视图来处理所有事件。


 _ = observable.subscribe(onNext: { (event) in print(event) }, onError: { (error) in print(error) }, onCompleted: { print("finish") }) { print("disposed") // ,          } 

   finish disposed 

可以观察到,您不仅可以从一行创建一个序列,而且不仅可以从一行创建一个序列,还可以在其中放置任何数据类型。


 let sequence = Observable<Int>.of(1, 2, 4, 5, 6) _ = sequence.subscribe { (event) in print(event) } 

 next(1) next(2) ... completed 

可以从值数组创建Observable。


 let array = [1, 2, 3] let observable = Observable<Int>.from(array) _ = observable.subscribe { (event) in print(event) } 

 next(1) next(2) next(3) completed 

一个可观察对象可以具有任意数量的Subscriber'ov 。 现在术语,什么是可观察的?


可观察是所有Rx的基础,Rx异步生成一系列不可变数据,并允许其他人订阅它。


处置


既然我们知道如何创建序列并订阅它们,我们就需要处理dispose


重要的是要记住, Observable是“冷”类型,也就是说,我们的Observable在订阅之前不会“发出”任何事件。 在发送错误消息或错误消息( complete )之前,observable存在。 如果要显式取消订阅,则可以执行以下操作。


 // №1 //   let array = [1, 2, 3] // observable    let observable = Observable<Int>.from(array) //   observable let subscription = observable.subscribe { (event) in print(event) } //dispos'    subscription.dispose() 

还有更多 美丽的 正确的选择。


 //  "" let bag = DisposeBag() //   let array = [1, 2, 3] // observable    let observable = Observable<Int>.from(array) //   observable _ = observable.subscribe { (event) in print(event) }.disposed(by: bag) 

因此,我们将订阅添加到回收袋或DisposeBag中
这是为了什么 如果使用订阅未将其添加到包中或未明确调用dispose ,或者在极端情况下,未以任何方式使Observable完成,那么很可能会发生内存泄漏。 在RxSwift中,您将经常使用DisposeBag。


经营者


功能反应式编程(以下为FRP)具有许多内置运算符,用于转换可观察元素。 那里有一个rxmarbles站点,您可以看到所有操作员的工作和效果,但是,我们仍然会看看其中的一些。


地图


map运算符经常使用,我认为许多人都很熟悉它,借助它,我们可以转换收到的所有元素。
一个例子:


 let bag = DisposeBag() let array = [1, 2, 3] let observable = Observable<Int>.from(array).map { $0 * 2 } _ = observable.subscribe { (e) in print(e) }.disponsed(by: bag) 

我们在控制台中得到什么:


 next(2) next(4) next(6) completed 

我们采用序列的每个元素,并创建一个新的结果序列。 为了使事情更清楚,最好写下更多。


 let bag = DisposeBag() let observable = Observable<Int>.from(array) //  observable let transformObservable = observable.map { $0 * 2 } _ = transformObservable.subscribe { (e) in print(e) }.disposed(by: bag) 

什么是$ 0?


$ 0是元素的名称,默认情况下,我们可以在方法中使用缩写记录和完整记录,最常见的是我们使用缩写记录。


 //  let transformObservable = observable.map { $0 * 2 } //  let transformObservable = observable.map { (element) -> Int in return element * 2 } 

同意简写形式要简单得多,对吗?


筛选条件


过滤运算符允许我们过滤掉可观察对象发出的数据,也就是说,在订阅时,我们不会为我们收到不必要的值。
一个例子:


 let bag = DisposeBag() let array = [1, 2, 3, 4, 5 , 6, 7] // observable   let observable = Observable<Int>.from(array) //  filter,     observable let filteredObservable = observable.filter { $0 > 2 } // _ = filteredObservable.subscribe { (e) in print(e) }.disposed(by: bag) 

我们在控制台中得到什么?


 next(3) next(4) next(5) ... completed 

如我们所见,在控制台中,只有那些满足我们条件的值。


顺便说一下,可以将运算符组合在一起,这就是如果我们要立即应用过滤运算符和地图运算符时的外观。


 let bag = DisposeBag() let array = [1, 2, 3, 4, 5 , 6, 7] let observable = Observable<Int>.from(array) let filteredAndMappedObservable = observable .filter { $0 > 2 } .map { $0 * 2 } _ = filteredAndMappedObservable.subscribe { (e) in print(e) }.disposed(by: bag) 

控制台:


 next(6) next(8) next(10) next(12) next(14) completed 

独特的


与过滤相关的另一个出色的运算符, disctinct运算符允许仅跳过更改的数据,最好立即查看示例,一切都会变得清楚。


 let bag = DisposeBag() let array = [1, 1, 1, 2, 3, 3, 5, 5, 6] let observable = Observable<Int>.from(array) let filteredObservable = observable.distinctUntilChanged() _ = filteredObservable.subscribe { (e) in print(e) }.disposed(by: bag) 

在控制台中,我们得到以下信息:


 next(1) next(2) next(3) next(5) next(6) completed 

也就是说,如果序列中的当前元素与前一个元素相同,则将其跳过,依此类推,直到出现与前一个元素不同的元素,这在处理UI(即表)时非常方便,以防我们收到数据与我们现在的相同,则不应该重新加载表。


Takelast


一个非常简单的takeLast运算符,我们从末尾开始获取第n个元素。


 let bag = DisposeBag() let array = [1, 1, 1, 2, 3, 3, 5, 5, 6] let observable = Observable<Int>.from(array) let takeLastObservable = observable.takeLast(1) _ = takeLastObservable.subscribe { (e) in print(e) }.disposed(by: bag) 

在控制台中,我们得到以下信息:


 next(6) completed 

节气门和间隔


然后,我决定一次雇用2位操作员,这是因为在第二个操作员的帮助下,很容易显示第一个操作员的工作。


节流运算符使您可以在捕获传输的值之间稍作休息,这是一个非常简单的示例,您编写了一个响应式应用程序,使用搜索栏,并且不想每次输入数据时都重新加载表或访问服务器,因此您使用了节流阀 ,因此说您是否想每2秒获取一次用户数据(例如,您可以设置任何时间间隔)并减少不必要的处理资源消耗,它如何工作并在代码中进行了描述? 请参阅下面的示例。


 let bag = DisposeBag() //observable     0.5    1   0 let observable = Observable<Int>.interval(0.5, scheduler: MainScheduler.instance) let throttleObservable = observable.throttle(1.0, scheduler: MainScheduler.instance) _ = takeLastObservable.subscribe { (event) in print("throttle \(event)") }.disposed(by: bag) 

在控制台中将是:


 throttle next(0) throttle next(2) throttle next(4) throttle next(6) ... 

间隔运算符使Observable可以从0.5开始每0.5秒以1为增量生成值,这是Rx的简单计时器。 事实证明,每隔0.5秒生成一次值,然后每秒即可生成2个值(简单的算法),节气门运算符将等待一秒钟并获取最后一个值。


去抖


Debounce与先前的声明非常相似,但在我看来有点聪明。 防反跳操作符等待第n个时间,如果它的计时器开始没有变化,它将采用最后一个值,如果我们发送该值,计时器将重新启动。 这对于上一个示例中描述的情况非常有用,用户输入数据,我们等待他完成操作(如果用户不活动一到两个半),然后我们开始执行一些操作。 因此,如果仅更改上一个代码中的运算符,则将无法在控制台中获取值,因为反跳将等待一秒钟,但是每0.5秒它将收到一个新值并重新启动其计时器,因此我们将一无所获。 让我们来看一个例子。


 let bag = DisposeBag() let observable = Observable<Int>.interval(1.5, scheduler: MainScheduler.instance) let debounceObservable = observable.debounce(1.0, scheduler: MainScheduler.instance) _ = debounceObservable.subscribe({ (e) in print("debounce \(e)") }).disposed(by: bag) 

在这个阶段,我建议结束操作员,在RxSwift框架中有很多操作员,不能说它们在日常生活中都是非常必要的,但是您仍然需要了解它们的存在,因此建议您熟悉rxmarbles网站上的操作员的完整列表。


排程器


我想在本文中谈到的一个非常重要的主题是调度程序。 调度程序,允许我们在某些线程上运行可观察的对象,它们具有自己的精妙之处。 首先,设置可观察的调度程序有2种类型-[observeOn]()和[subscribeOn]()。


订阅


SubscribeOn负责整个观察过程将在其中运行的线程,直到其事件到达处理程序(订阅者)为止。


观察


您可能会猜到,observeOn负责在哪个流中处理订阅者收到的事件。


这是一件很酷的事情,因为我们可以很容易地将网络上的内容下载到后台流中,并在接收到结果后转到主流并以某种方式在UI上进行操作。


让我们来看一个例子:


 let observable = Observable<Int>.create { (observer) -> Disposable in print("thread observable -> \(Thread.current)") observer.onNext(1) observer.onNext(2) return Disposables.create() }.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) _ = observable .observeOn(MainScheduler.instance) .subscribe({ (e) in print("thread -> \(Thread.current)") print(e) }) 

在控制台中,我们得到:


 thread observable -> <NSThread: 0x604000465040>{number = 3, name = (null)} thread -> <NSThread: 0x60000006f6c0>{number = 1, name = main} next(1) thread -> <NSThread: 0x60000006f6c0>{number = 1, name = main} next(2) 

我们看到在后台线程中创建了observable,并且在主线程中处理了数据。 在使用网络时,这很有用,例如:


 let rxRequest = URLSession.shared.rx.data(request: URLRequest(url: URL(string: "http://jsonplaceholder.typicode.com/posts/1")!)).subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) _ = rxRequest .observeOn(MainScheduler.instance) .subscribe { (event) in print(" \(event)") print("thread \(Thread.current)") } 

因此,该请求将在后台线程中执行,并且所有响应处理都将在主线程中进行。 在这个阶段,现在说出什么样的 rx方法URLSession突然产生为时过早 ,这将在后面讨论,该代码作为使用Scheduler的示例给出,顺便说一下,我们将对控制台获得以下响应。


 curl -X GET "http://jsonplaceholder.typicode.com/posts/1" -i -v Success (305ms): Status 200 ** next(292 bytes)** thread -> <NSThread: 0x600000072580>{number = 1, name = main}  completed thread -> <NSThread: 0x600000072580>{number = 1, name = main} 

在结局中,让我们看看收到了什么样的数据 ,为此,我们将必须执行检查,以免意外地分析完成的消息。


 _ = rxRequest .observeOn(MainScheduler.instance) .subscribe { (event) in if (!event.isCompleted && event.error == nil) { let json = try? JSONSerialization.jsonObject(with: event.element!, options: []) print(json!) } print("data -> \(event)") print("thread -> \(Thread.current)") } 

尽管可以实现不同的订阅方法并单独处理所有这些类型的事件,但我们检查该事件不是可观察到的关闭消息或来自它的错误,但是您可以自己执行此操作,我们将在控制台中提供以下内容。


 curl -X GET "http://jsonplaceholder.typicode.com/posts/1" -i -v Success (182ms): Status 200 { body = "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"; id = 1; title = "sunt aut facere repellat provident occaecati excepturi optio reprehenderit"; userId = 1; } data -> next(292 bytes) thread -> <NSThread: 0x60400006c6c0>{number = 1, name = main} data -> completed thread -> <NSThread: 0x60400006c6c0>{number = 1, name = main} 

收到的数据:-)


科目


我们转向热点,即从可观察到的“冷”或“被动”到可观察到的“热点”或“主动”,这被称为主体。 如果在此之前,我们的观察员只有在订阅他们之后才开始工作,而您的脑子里有一个问题,“为什么我需要所有这些?”,那么主题总是工作,并且总是发送接收到的数据。


怎么了 在可观察的情况下,我们去了诊所,去了邪恶的奶奶 接待处 到接待处,他们走近,问我们应该去哪个办公室,然后颗粒剂回答了我们。如果是受试者,颗粒剂会站立并听取医生在医院的时间表和状况,并在收到有关任何医生活动的信息后立即说,问某些东西对于制粒是无用的,我们可以上来,听她的话,离开,然后她会继续说,通过比较,有些东西被带走了,让我们开始编写代码。


让我们创建一个主题和2个订阅者,在主题之后创建第一个主题,将值发送给主题,然后创建第二个主题并发送另外两个值。


 let subject = PublishSubject<Int>() subject.subscribe { (event) in print("  \(event)") } subject.onNext(1) _ = subject.subscribe { (event) in print("  \(event)") } subject.onNext(2) subject.onNext(3) subject.onNext(4) 

我们将在控制台中看到什么? 是的,第一个设法获得了第一个事件,但是第二个却没有。


   next(1)   next(2)   next(2)   next(3)   next(3)   next(4)   next(4) 

已经更适合您的反应式编程想法了吗?
主题有几种形式;它们在发送值的方式上都不同。


PublishSubject是最简单的一个,它对所有内容都无关紧要,它只是向所有订阅者发送所收到的信息,而忘记了它。


ReplaySubject-这是最重要的,在创建时,我们告诉它缓冲区的大小(将记住多少个值),结果,它将最后n个值存储在内存中,并将它们立即发送给新的订户。


 let subject = ReplaySubject<Int>.create(bufferSize: 3) subject.subscribe { (event) in print("  \(event)") } subject.onNext(1) subject.subscribe { (event) in print("  \(event)") } subject.onNext(2) subject.onNext(3) subject.subscribe { (event) in print("  \(event)") } subject.onNext(4) 

我们看一下控制台


   next(1)   next(1)   next(2)   next(2)   next(3)   next(3)   next(1)   next(2)   next(3)   next(4)   next(4)   next(4) 

BehaviorSubject不是上一个废话,它有一个起始值,它会记住最后一个值,并在订阅者订阅后立即发送。


 let subject = BehaviorSubject<Int>(value: 0) subject.subscribe { (event) in print("  \(event)") } subject.onNext(1) subject.subscribe { (event) in print("  \(event)") } subject.onNext(2) subject.onNext(3) subject.subscribe { (event) in print("  \(event)") } subject.onNext(4) 

主控台


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

结论


这是一篇介绍性文章,以便您了解基础知识并随后可以在其基础上进行构建。 在以下文章中,我们将研究如何通过iOS UI组件使用RxSwift,并为UI组件创建扩展。


不RxSwift'om团结


响应式编程不仅在RxSwift库中实现,还有几种实现,其中最流行的是ReacktiveKit / BondReactiveSwift / ReactiveCocoa 。 它们在幕后的实现方式上都有细微的差别,但是我认为最好使用RxSwift来开始对reactivism的了解,因为它是其中最受欢迎的一种,并且在出色的Google中会有更多的答案,但是在您之后了解此概念的本质后,您可以根据自己的喜好和颜色选择资料库。
文章作者:Pavel Grechikhin

Source: https://habr.com/ru/post/zh-CN423603/


All Articles