RxSwift partie 1

ReactiveX logo


Bonjour, Habrovsk. Dans cette série d'articles, je voudrais parler de la programmation réactive, à savoir le cadre
RxSwift . Il y avait des articles sur RxSwift sur Habré et sur le réseau, mais, à mon avis, ils sont trop difficiles pour les débutants. Par conséquent, si vous commencez à comprendre la programmation réactive dans iOS, alors je demande cat.


Commençons par définir ce qu'est la programmation réactive.


La programmation réactive est un paradigme de programmation axé sur les flux de données et la propagation du changement.

C'est ce que nous dit le grand Wikipédia .


En d'autres termes, dans le cas où nous programmons dans un style impératif, nous écrivons dans le code un ensemble de commandes qui doivent être exécutées séquentiellement. Le style de programmation réactif adhère à plusieurs autres concepts. Avec un style de programmation réactif, notre programme est un «écouteur» des changements d'état dans nos objets observés. Cela semble compliqué, mais ce n'est pas le cas, il suffit de pénétrer ce concept et tout deviendra extrêmement facile et clair, pas encore de bugs .


Je ne peindrai pas comment installer le framework, c'est facile à faire en cliquant sur le lien . Passons à la pratique.


Observable


Commençons par un observable ou observable simple mais important. Observable est ce qui nous donnera les données, il est nécessaire de générer un flux de données.


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

BINGO ! Nous avons créé le premier observable.


alors quoi?


Puisque nous avons créé l'objet observé, il est logique que nous devions créer un objet qui observera.


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

nous obtenons ce qui suit dans le journal:


 next( observable) completed 

terminé?


Observable nous envoie des informations sur ses événements, il n'y en a que 3 types:


  • suivant
  • erreur
  • terminé

Avec l'élément suivant vient l'élément que nous avons envoyé et tous les événements envoyés par nous, l' erreur est envoyée comme son nom l'indique en cas d'erreur, et complétée dans le cas où notre observable a envoyé toutes les données et se termine.


Nous pouvons créer plus de détails observateur abonné et obtenir une vue plus pratique pour gérer tous les événements.


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

   finish disposed 

En observable, vous pouvez créer une séquence non seulement à partir d'une ligne, et en effet non seulement à partir de lignes, nous pouvons y mettre n'importe quel type de données.


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

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

L'observable peut être créé à partir d'un tableau de valeurs.


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

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

Un observable peut avoir n'importe quel nombre d' abonnés . Maintenant la terminologie, qu'est-ce qu'un observable?


Observable est le fondement de tout Rx, qui génère de manière asynchrone une séquence de données immuables et permet à d'autres de s'y abonner.


Élimination


Maintenant que nous savons comment créer une séquence et y souscrire, nous devons faire face à une chose telle que l' élimination .


Il est important de se rappeler qu'Observable est un type "froid", c'est-à-dire que notre observable "n'émet" aucun événement tant qu'il n'est pas souscrit. Observable existe jusqu'à ce qu'il envoie un message d'erreur ou un message d' erreur ( terminé ). Si nous voulons annuler explicitement l'abonnement, nous pouvons procéder comme suit.


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

Il y a plus beau bonne 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) 

Ainsi, nous ajoutons notre abonnement au sac de recyclage ou au DisposeBag .
À quoi ça sert? Si vous, en utilisant un abonnement, ne l'ajoutez pas à votre sac ou si vous n'appelez pas explicitement disposer , ou, dans les cas extrêmes, vous n'amenez pas observable à la fin de quelque manière que ce soit, alors vous obtiendrez probablement une fuite de mémoire. Vous utiliserez DisposeBag très souvent dans votre travail avec RxSwift.


Les opérateurs


La programmation fonctionnelle-réactive (FRP ci-dessous) a de nombreux opérateurs intégrés pour transformer les éléments observables. Il y a un site rxmarbles , sur lequel vous pouvez voir le travail et l'effet de tous les opérateurs, bien, mais nous regardons toujours certains d'entre eux.


La carte


L'opérateur de carte est utilisé très souvent et je pense qu'il est familier à beaucoup, avec son aide, nous transformons tous les éléments reçus.
Un exemple:


 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) 

Qu'obtenons-nous dans la console:


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

Nous prenons chaque élément de la séquence et créons une nouvelle séquence résultante. Pour clarifier ce qui se passe, il vaut mieux écrire davantage.


 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) 

Qu'est-ce que 0 $?


$ 0 est le nom de l'élément par défaut, nous pouvons utiliser des enregistrements abrégés et complets dans les méthodes, le plus souvent nous utilisons des abréviations


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

Conviens que la forme courte est beaucoup plus simple, non?


Filtrer


L'opérateur de filtre nous permet de filtrer les données émises par notre observable, c'est-à-dire que lors de la souscription, nous ne recevrons pas de valeurs inutiles pour nous.
Un exemple:


 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) 

Qu'obtenons-nous dans la console?


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

Comme nous le voyons, dans la console, nous n'avons que les valeurs qui satisfont nos conditions.


Soit dit en passant, les opérateurs peuvent être combinés, voici à quoi cela ressemblerait si nous voulions appliquer immédiatement l'opérateur de filtrage et l'opérateur de carte .


 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) 

Console:


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

Distinct


Un autre excellent opérateur associé au filtrage, l'opérateur discret vous permet de ne sauter que les données modifiées, il est préférable de se tourner immédiatement vers un exemple et tout deviendra clair.


 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) 

Dans la console, nous obtenons ce qui suit:


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

c'est-à-dire que si l'élément actuel de la séquence est identique au précédent, il est ignoré et ainsi de suite jusqu'à ce qu'un élément différent du précédent apparaisse, cela est très pratique lorsque vous travaillez avec, par exemple, l'interface utilisateur, à savoir la table, au cas où nous aurions reçu des données sont les mêmes que nous avons maintenant, alors recharger la table ne devrait pas être.


Takelast


Un opérateur takeLast très simple, nous prenons le nième nombre d'éléments de la fin.


 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) 

Dans la console, nous obtenons ce qui suit:


 next(6) completed 

Accélérateur et intervalle


J'ai alors décidé de prendre 2 opérateurs à la fois, tout simplement parce qu'avec l'aide du second, il est facile de montrer le travail du premier.


L'opérateur d' accélérateur vous permet de faire une pause entre la capture des valeurs transmises, c'est un exemple très simple, vous écrivez une application réactive, utilisez la barre de recherche et ne voulez pas recharger la table ou aller sur le serveur à chaque fois que vous entrez des données, vous utilisez donc l' accélérateur et dites donc que Voulez-vous prendre les données utilisateur toutes les 2 secondes (par exemple, vous pouvez définir n'importe quel intervalle) et réduire la consommation de ressources pour un traitement inutile, comment cela fonctionne-t-il et est-il décrit dans le code? Voir ci-dessous pour un exemple.


 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) 

Dans la console sera:


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

L'opérateur d' intervalle fait que l'observable génère des valeurs toutes les 0,5 secondes par incréments de 1 à partir de 0, ce qui est un minuteur si simple pour Rx. Il s'avère qu'une fois que les valeurs sont générées toutes les 0,5 secondes, alors 2 valeurs sont générées par seconde, arithmétique simple, et l'opérateur de papillon attend une seconde et prend la dernière valeur.


Debounce


Debounce est très similaire à la déclaration précédente, mais un peu plus intelligent à mon avis. L'opérateur anti-rebond attend la nième durée, et s'il n'y a pas de changement depuis le début de son temporisateur, il prend la dernière valeur, si nous envoyons la valeur, le temporisateur redémarrera à nouveau. Ceci est juste très utile pour la situation décrite dans l'exemple précédent, l'utilisateur entre des données, nous attendons quand il a terminé (si l'utilisateur est inactif pendant une seconde ou demie), puis nous commençons à effectuer une action. Par conséquent, si nous changeons simplement l'opérateur dans le code précédent, nous n'obtiendrons pas les valeurs à la console, car debounce attendra une seconde, mais toutes les 0,5 secondes, il recevra une nouvelle valeur et redémarrera son temporisateur, donc nous n'obtiendrons rien. Voyons un exemple.


 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) 

À ce stade, je propose de terminer avec les opérateurs, ils sont nombreux dans le framework RxSwift, on ne peut pas dire qu'ils sont tous très nécessaires dans la vie de tous les jours, mais il faut quand même connaître leur existence, il est donc conseillé de se familiariser avec la liste complète des opérateurs sur le site rxmarbles .


Planificateur


Un sujet très important que je voudrais aborder dans cet article est le planificateur. Scheduler, nous permet d'exécuter notre observable sur certains threads et ils ont leurs propres subtilités. Pour commencer, il existe 2 types de paramétrage du planificateur observable - [observOn] () et [subscribeOn] ().


Abonnez-vous


SubscribeOn est responsable du thread dans lequel tout le processus observable s'exécutera jusqu'à ce que ses événements atteignent le gestionnaire (abonné).


Observeon


Comme vous pouvez le deviner, observeOn est responsable du flux dans lequel les événements reçus par l'abonné seront traités.


C'est une chose très cool, car nous pouvons très facilement mettre le téléchargement de quelque chose du réseau dans le flux d'arrière-plan, et lorsque nous obtenons le résultat, allez dans le flux principal et agissez en quelque sorte sur l'interface utilisateur.


Voyons comment cela fonctionne avec un exemple:


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

Dans la console, nous obtenons:


 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) 

Nous voyons que l'observable a été créé dans le thread d'arrière-plan et nous avons traité les données dans le thread principal. Ceci est utile lorsque vous travaillez avec un réseau, par exemple:


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

Ainsi, la demande sera exécutée dans le thread d'arrière-plan et tout le traitement des réponses se fera en principal. À ce stade, il est trop tôt pour dire quel type de méthode rx URLSession a soudainement dessiné, cela sera discuté plus tard, ce code a été donné comme exemple d'utilisation de Scheduler , en passant, nous obtiendrons la réponse suivante à la console.


 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} 

Dans la finale, voyons quel type de données nous est parvenu, pour cela nous devrons effectuer une vérification afin de ne pas commencer à analyser le message terminé par accident.


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

Nous vérifions que l'événement n'est pas un message d'arrêt observable ou une erreur qui en est issue, bien qu'il ait été possible d'implémenter une méthode d'abonnement différente et de traiter tous ces types d'événements séparément, mais vous pouvez déjà le faire vous-même, et nous obtiendrons les éléments suivants dans la console.


 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} 

Données reçues :-)


Sujets


Nous passons au chaud, à savoir du "froid" ou "passif" observable au "chaud" ou "actif" observable, que l'on appelle subject'ami. Si avant cela notre observable n'a commencé son travail qu'après s'être abonné à eux et que vous aviez une question dans votre tête, "pourquoi ai-je besoin de tout cela?", Alors les sujets travaillent toujours et envoient toujours les données reçues.


Comment est-ce? Dans le cas de observable, nous sommes allés à la clinique, sommes allés à la grand-mère maléfique sur réception à la réception, ils se sont approchés et ont demandé à quel bureau nous devrions aller, puis la granulation nous a répondu, dans le cas des sujets, la granulation se tient et écoute le calendrier et l'état des médecins à l'hôpital et dès qu'il reçoit des informations sur le mouvement d'un médecin, il dit immédiatement ceci, demandez Quelque chose est inutile pour la granulation, nous pouvons simplement venir, l'écouter, partir, et elle continuera à dire, quelque chose est emporté par des comparaisons, passons au code.


Créons un sujet et 2 abonnés, créons le premier juste après le sujet, envoyons la valeur au sujet, puis créons le second et envoyons quelques valeurs supplémentaires.


 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) 

Que verrons-nous dans la console? à droite, le premier a réussi à obtenir le premier événement, mais pas le second.


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

Déjà plus adapté à votre idée de programmation réactive?
Les sujets se présentent sous plusieurs formes; ils diffèrent tous par la façon dont ils envoient des valeurs.


PublishSubject est le plus simple, il n'a pas d'importance pour tout, il envoie simplement à tous les abonnés ce qu'il en est venu et l'oublie.


ReplaySubject - mais c'est le plus important, lors de la création, nous lui indiquons la taille du tampon (combien de valeurs seront mémorisées), par conséquent, il stocke les n dernières valeurs en mémoire et les envoie immédiatement au nouvel abonné.


 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) 

Nous regardons la console


   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 n'est pas un non-sens comme le précédent, il a une valeur de début et il se souvient de la dernière valeur et l'envoie immédiatement après que l'abonné s'est abonné.


 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) 

La console


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

Conclusion


Il s'agissait d'un article d'introduction écrit pour que vous connaissiez les bases et que vous puissiez ensuite les développer. Dans les articles suivants, nous verrons comment travailler avec RxSwift avec les composants de l'interface utilisateur iOS, en créant des extensions pour les composants de l'interface utilisateur.


Pas RxSwift'om uni


La programmation réactive est implémentée non seulement dans la bibliothèque RxSwift, il existe plusieurs implémentations, voici les plus populaires d'entre elles ReacktiveKit / Bond , ReactiveSwift / ReactiveCocoa . Ils ont tous de petites différences de mise en œuvre sous le capot, mais à mon avis, il est préférable de commencer vos connaissances sur le réactivisme avec RxSwift, car il est le plus populaire parmi eux et il y aura plus de réponses dans grand Google , mais après vous comprendre l'essence de ce concept, vous pouvez choisir des bibliothèques à votre goût et couleur.
Auteur de l'article: Pavel Grechikhin

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


All Articles