RxSwift Teil 1

ReactiveX logo


Guten Tag, Habrovsk. In dieser Artikelserie möchte ich über reaktive Programmierung sprechen, nämlich das Framework
RxSwift . Es gab Artikel über RxSwift auf Habré und im Netzwerk, aber meiner Meinung nach sind sie für Anfänger zu schwierig. Wenn Sie anfangen, reaktive Programmierung in iOS zu verstehen, frage ich nach Katze.


Beginnen wir mit der Definition der reaktiven Programmierung.


Reaktive Programmierung ist ein Programmierparadigma, das sich auf Datenflüsse und die Ausbreitung von Veränderungen konzentriert.

Das sagt uns die große Wikipedia .


Mit anderen Worten, wenn wir in einem imperativen Stil programmieren, schreiben wir in den Code eine Reihe von Befehlen, die nacheinander ausgeführt werden müssen. Der reaktive Programmierstil folgt mehreren anderen Konzepten. Mit einem reaktiven Programmierstil ist unser Programm ein „Zuhörer“ von Zustandsänderungen in unseren beobachteten Objekten. Es klingt kompliziert, aber es ist nicht genug, um dieses Konzept zu durchdringen, und alles wird extrem einfach und klar. Noch keine Bugs .


Ich werde nicht malen, wie das Framework installiert wird, es ist einfach, indem Sie auf den Link klicken. Lass uns üben.


Beobachtbar


Beginnen wir mit einem einfachen, aber wichtigen Beobachtbaren oder Beobachtbaren. Beobachtbar ist, was uns die Daten gibt, es wird benötigt, um einen Datenstrom zu erzeugen.


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

BINGO ! Wir haben das erste Observable erstellt.


Na und?


Da wir das beobachtete Objekt erstellt haben, ist es logisch, dass wir ein Objekt erstellen müssen, das beobachtet.


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

Im Protokoll wird Folgendes angezeigt:


 next( observable) completed 

abgeschlossen?


Observable sendet uns Informationen über seine Ereignisse, es gibt nur 3 Arten:


  • als nächstes
  • Fehler
  • abgeschlossen

Zusammen mit dem nächsten Element, dem von uns gesendeten Element und allen von uns gesendeten Ereignissen, wird ein Fehler gesendet, wie der Name im Fehlerfall impliziert, und in dem Fall abgeschlossen, in dem unser Observable alle Daten gesendet und beendet hat.


Wir können detaillierter erstellen Beobachter Abonnenten und erhalten eine bequemere Ansicht, um alle Ereignisse zu behandeln.


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

   finish disposed 

In Observable können Sie eine Sequenz nicht nur aus einer Zeile erstellen, sondern auch nicht nur aus Zeilen. Wir können dort jeden Datentyp einfügen.


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

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

Observable kann aus einem Array von Werten erstellt werden.


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

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

Ein Observable kann eine beliebige Anzahl von Abonnenten haben . Was ist nun ein Observable?


Observable ist die Grundlage aller Rx, die asynchron eine Folge unveränderlicher Daten generieren und es anderen ermöglichen, diese zu abonnieren.


Entsorgen


Jetzt, da wir wissen, wie man eine Sequenz erstellt und abonniert, müssen wir uns mit so etwas wie Entsorgen befassen.


Es ist wichtig, sich daran zu erinnern, dass Observable ein "kalter" Typ ist, dh unser Observable "emittiert" keine Ereignisse, bis es abonniert ist. Observable ist vorhanden, bis eine Fehlermeldung oder eine Fehlermeldung ( abgeschlossen ) gesendet wird. Wenn wir das Abonnement explizit kündigen möchten, können wir Folgendes tun.


 // №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() 

Es gibt noch mehr schön richtige Option.


 //  "" 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) 

Daher fügen wir unser Abonnement dem Recyclingbeutel oder dem DisposeBag hinzu .
Wofür ist das? Wenn Sie ein Abonnement verwenden, es nicht zu Ihrer Tasche hinzufügen oder explizit nicht dispose aufrufen oder in extremen Fällen das Beobachtbare in keiner Weise zum Abschluss bringen, wird höchstwahrscheinlich ein Speicherverlust auftreten. Sie werden DisposeBag sehr oft in Ihrer Arbeit mit RxSwift verwenden.


Betreiber


Die funktional-reaktive Programmierung (FRP unten) verfügt über viele integrierte Operatoren zum Transformieren beobachtbarer Elemente. Es gibt eine Site rxmarbles , auf der Sie die Arbeit und die Wirkung aller Bediener sehen können, aber wir sehen uns noch einige davon an.


Karte


Der Kartenoperator wird sehr oft verwendet und ich denke, dass er vielen bekannt ist. Mit seiner Hilfe transformieren wir alle empfangenen Elemente.
Ein Beispiel:


 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) 

Was bekommen wir in der Konsole:


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

Wir nehmen jedes Element der Sequenz und erstellen eine neue resultierende Sequenz. Um klarer zu machen, was los ist, ist es besser, mehr aufzuschreiben.


 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) 

Was ist $ 0?


$ 0 ist standardmäßig der Name des Elements. Wir können abgekürzte und vollständige Datensätze in Methoden verwenden. Meistens verwenden wir Abkürzungen.


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

Stimmen Sie zu, dass die Kurzform viel einfacher ist, oder?


Filter


Mit dem Filteroperator können wir die von unserem Observable ausgegebenen Daten herausfiltern, dh beim Abonnieren erhalten wir keine unnötigen Werte für uns.
Ein Beispiel:


 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) 

Was bekommen wir in der Konsole?


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

Wie wir sehen, haben wir in der Konsole nur die Werte, die unseren Bedingungen entsprechen.


Übrigens können die Operatoren kombiniert werden. So würde es aussehen, wenn wir sowohl den Filteroperator als auch den Kartenoperator sofort anwenden möchten.


 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) 

Konsole:


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

Deutlich


Ein weiterer hervorragender Operator, der mit dem Filtern verbunden ist. Mit dem disctinct- Operator können Sie nur die geänderten Daten überspringen. Wenden Sie sich am besten sofort einem Beispiel zu, und alles wird klar.


 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) 

In der Konsole erhalten wir Folgendes:


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

Das heißt, wenn das aktuelle Element der Sequenz mit dem vorherigen identisch ist, wird es übersprungen usw., bis ein anderes Element als das vorherige angezeigt wird. Dies ist sehr praktisch, wenn Sie beispielsweise mit der Benutzeroberfläche, nämlich der Tabelle, arbeiten, falls wir Daten erhalten haben sind die gleichen wie wir jetzt haben, dann sollte das Nachladen der Tabelle nicht sein.


Takelast


Als sehr einfacher takeLast- Operator nehmen wir die n-te Anzahl von Elementen vom Ende.


 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) 

In der Konsole erhalten wir Folgendes:


 next(6) completed 

Gas und Intervall


Dann habe ich beschlossen, 2 Operatoren gleichzeitig zu nehmen, einfach weil es mit Hilfe des zweiten einfach ist, die Arbeit des ersten zu zeigen.


Mit dem Drosselklappenoperator können Sie eine Pause zwischen der Erfassung der übertragenen Werte einlegen. Dies ist ein sehr einfaches Beispiel. Sie schreiben eine reaktive Anwendung, verwenden die Suchleiste und möchten nicht jedes Mal, wenn Sie Daten eingeben, die Tabelle neu laden oder zum Server gehen. Verwenden Sie also Drosselklappe und sagen Sie dies Möchten Sie Benutzerdaten alle 2 Sekunden erfassen (z. B. können Sie ein beliebiges Intervall festlegen) und den Ressourcenverbrauch für unnötige Verarbeitung reduzieren. Wie funktioniert das und wird im Code beschrieben? Ein Beispiel finden Sie weiter unten.


 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) 

In der Konsole wird sein:


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

Der Intervalloperator veranlasst Observable, alle 0,5 Sekunden Werte in Schritten von 1 ab 0 zu generieren, was für Rx ein so einfacher Timer ist. Es stellt sich heraus, dass, sobald die Werte alle 0,5 Sekunden generiert wurden, 2 Werte pro Sekunde generiert werden, einfache Arithmetik, und der Drosselklappenbediener eine Sekunde wartet und den letzten Wert nimmt.


Entprellen


Debounce ist der vorherigen Aussage sehr ähnlich, aber meiner Meinung nach etwas schlauer. Der Entprellungsoperator wartet auf die n-te Zeit und wenn sich seit dem Start seines Timers keine Änderungen ergeben, nimmt er den letzten Wert an. Wenn wir den Wert senden, wird der Timer erneut gestartet. Dies ist nur sehr nützlich für die im vorherigen Beispiel beschriebene Situation. Der Benutzer gibt Daten ein, wir warten, bis er fertig ist (wenn der Benutzer eine oder eine halbe Sekunde inaktiv ist), und dann beginnen wir mit einer Aktion. Wenn wir also einfach den Operator im vorherigen Code ändern, erhalten wir die Werte nicht in der Konsole, da das Entprellen eine Sekunde wartet, aber alle 0,5 Sekunden einen neuen Wert erhält und den Timer neu startet, sodass wir nichts erhalten. Sehen wir uns ein Beispiel an.


 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) 

In dieser Phase schlage ich vor, mit den Betreibern fertig zu werden. Es gibt viele von ihnen im RxSwift-Framework. Man kann nicht sagen, dass sie alle im täglichen Leben sehr notwendig sind, aber Sie müssen immer noch über ihre Existenz Bescheid wissen. Daher ist es ratsam, sich mit der vollständigen Liste der Betreiber auf der rxmarbles- Website vertraut zu machen.


Scheduler


Ein sehr wichtiges Thema, das ich in diesem Artikel ansprechen möchte, ist der Scheduler. Scheduler, erlauben Sie uns, unser Observable auf bestimmten Threads auszuführen, und sie haben ihre eigenen Feinheiten. Zu Beginn gibt es zwei Arten der Einstellung des beobachtbaren Schedulers: [watchOn] () und [subscribeOn] ().


Abonnieren


SubscribeOn ist für den Thread verantwortlich, in dem der gesamte beobachtbare Prozess ausgeführt wird, bis seine Ereignisse den Handler (Abonnenten) erreichen.


Beobachten Sie


Wie Sie sich vorstellen können, ist watchOn dafür verantwortlich, in welchem ​​Stream die vom Abonnenten empfangenen Ereignisse verarbeitet werden.


Dies ist eine sehr coole Sache, da wir den Download von etwas aus dem Netzwerk sehr einfach in den Hintergrund-Stream stellen und nach Erhalt des Ergebnisses zum Haupt-Stream gehen und irgendwie auf die Benutzeroberfläche reagieren können.


Mal sehen, wie das mit einem Beispiel funktioniert:


 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) }) 

In der Konsole erhalten wir:


 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) 

Wir sehen, dass Observable im Hintergrund-Thread erstellt wurde, und wir haben die Daten im Haupt-Thread verarbeitet. Dies ist nützlich, wenn Sie mit einem Netzwerk arbeiten, zum Beispiel:


 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)") } 

Somit wird die Anforderung im Hintergrundthread ausgeführt, und die gesamte Antwortverarbeitung erfolgt in main. Zu diesem Zeitpunkt ist es noch zu früh zu sagen, welche Art von Empfangsmethode URLSession plötzlich gezeichnet hat. Dies wird später erläutert. Dieser Code wurde als Beispiel für die Verwendung von Scheduler angegeben. Übrigens erhalten wir die folgende Antwort auf die Konsole.


 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} 

Lassen Sie uns im Finale sehen, welche Art von Daten zu uns gekommen sind. Dazu müssen wir eine Überprüfung durchführen, um die fertige Nachricht nicht versehentlich zu analysieren.


 _ = 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)") } 

Wir stellen sicher, dass es sich bei dem Ereignis nicht um eine beobachtbare Meldung zum Herunterfahren oder einen daraus resultierenden Fehler handelt. Es war zwar möglich, eine andere Abonnementmethode zu implementieren und alle diese Ereignistypen separat zu verarbeiten. Sie können dies jedoch bereits selbst tun. In der Konsole wird Folgendes angezeigt.


 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} 

Erhaltene Daten :-)


Themen


Wir wenden uns dem Heißen zu, nämlich vom "kalten" oder "passiven" beobachtbaren zum "heißen" oder "aktiven" beobachtbaren, die Subjekt'ami genannt werden. Wenn unsere Observable zuvor ihre Arbeit erst nach dem Abonnieren begonnen haben und Sie eine Frage im Kopf hatten: „Nun, warum brauche ich das alles?“, Dann funktionieren die Probanden immer und senden immer die empfangenen Daten.


Wie ist es? Im Falle von Observable gingen wir in die Klinik, gingen weiter zur bösen Oma Rezeption An der Rezeption näherten sie sich und fragten, in welches Büro wir gehen sollten. Dann antwortete uns die Granulation. Bei Probanden steht die Granulation und hört auf den Zeitplan und den Zustand der Ärzte im Krankenhaus. Sobald sie Informationen über die Bewegung eines Arztes erhält, sagt er dies sofort Etwas ist für die Granulierung nutzlos, wir können einfach auftauchen, ihr zuhören, gehen, und sie wird weiterhin sagen, etwas wird mit Vergleichen mitgerissen, lasst uns zum Code kommen.


Erstellen Sie einen Betreff und zwei Abonnenten, erstellen Sie den ersten unmittelbar nach dem Betreff, senden Sie dem Betreff einen Wert, erstellen Sie einen zweiten und senden Sie ein paar weitere Werte.


 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) 

Was sehen wir in der Konsole? Richtig, der erste hat das erste Event geschafft, der zweite nicht.


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

Schon besser für Ihre Vorstellung von reaktiver Programmierung geeignet?
Es gibt verschiedene Formen von Themen, die sich alle darin unterscheiden, wie sie Werte senden.


PublishSubject ist das einfachste, es ist nicht für alle wichtig, es sendet einfach an alle Abonnenten, was es erreicht hat, und vergisst es.


ReplaySubject - dies ist jedoch das Wichtigste. Beim Erstellen wird die Größe des Puffers angegeben (wie viele Werte werden gespeichert). Dadurch werden die letzten n Werte im Speicher gespeichert und sofort an den neuen Abonnenten gesendet.


 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) 

Wir schauen auf die Konsole


   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 ist kein Unsinn wie der vorherige, es hat einen Startwert und merkt sich den letzten Wert und sendet ihn sofort, nachdem der Abonnent ihn abonniert hat.


 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) 

Konsole


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

Fazit


Dies war ein Einführungsartikel, der geschrieben wurde, damit Sie die Grundlagen kennen und anschließend darauf aufbauen können. In den folgenden Artikeln wird erläutert, wie Sie mit RxSwift mit iOS-UI-Komponenten arbeiten und Erweiterungen für UI-Komponenten erstellen.


Nicht RxSwift'om vereint


Reaktive Programmierung ist nicht nur in der RxSwift-Bibliothek implementiert, es gibt auch mehrere Implementierungen, von denen die beliebtesten ReacktiveKit / Bond , ReactiveSwift / ReactiveCocoa sind . Sie alle haben kleine Unterschiede in der Implementierung unter der Haube, aber meiner Meinung nach ist es besser, Ihr Wissen über Reaktivismus mit RxSwift zu beginnen, da es unter ihnen am beliebtesten ist und es in großartigem Google mehr Antworten geben wird, aber nach Ihnen Wenn Sie die Essenz dieses Konzepts verstehen, können Sie Bibliotheken nach Ihrem Geschmack und Ihrer Farbe auswählen.
Artikelautor: Pavel Grechikhin

Source: https://habr.com/ru/post/de423603/


All Articles