RxSwift parte 1

ReactiveX logo


Bom dia, Habrovsk. Nesta série de artigos, eu gostaria de falar sobre programação reativa, a saber, o framework
RxSwift . Havia artigos sobre o RxSwift no Habré e na rede, mas, na minha opinião, eles são muito difíceis para iniciantes. Portanto, se você começar a entender a programação reativa no iOS, peço um gato.


Vamos começar definindo o que é programação reativa.


A programação reativa é um paradigma de programação focado nos fluxos de dados e na disseminação da mudança.

É isso que a grande Wikipedia nos diz.


Em outras palavras, quando programamos em um estilo imperativo, escrevemos no código um conjunto de comandos que devem ser executados seqüencialmente. O estilo de programação reativa adere a vários outros conceitos. Com um estilo de programação reativa, nosso programa é um "ouvinte" de alterações de estado em nossos objetos observados. Parece complicado, mas não é, basta apenas penetrar nesse conceito e tudo ficará extremamente fácil e claro, ainda não há erros .


Não vou pintar como instalar o framework, é fácil fazer isso clicando no link . Vamos praticar.


Observável


Vamos começar com um simples mas importante observável ou observável. Observável é o que nos dará os dados, é necessário gerar um fluxo de dados.


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

BINGO ! Criamos o primeiro observável.


e daí?


Desde que criamos o objeto observado, é lógico que precisamos criar um objeto que irá observar.


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

obtemos o seguinte no log:


 next( observable) completed 

concluída?


Observable nos envia informações sobre seus eventos, existem apenas 3 tipos:


  • próximo
  • erro
  • concluído

Juntamente com o próximo elemento, vem o elemento que enviamos e todos os eventos enviados por nós, error é enviado como o nome implica no evento de um erro e é concluído no caso em que nosso observável envia todos os dados e termina.


Podemos criar mais detalhes observador subscriber'a e obtenha uma visão mais conveniente para lidar com todos os eventos.


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

   finish disposed 

Em observável, você pode criar uma sequência não apenas de uma linha e, de fato, não apenas de linhas, podemos colocar qualquer tipo de dado lá.


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

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

Observável pode ser criado a partir de uma matriz de valores.


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

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

Um observável pode ter qualquer número de assinante . Agora terminologia, o que é um Observável?


Observable é a base de todo Rx, que gera assincronamente uma sequência de dados imutáveis ​​e permite que outras pessoas se inscrevam nele.


Descartando


Agora que sabemos como criar uma sequência e assiná-la, precisamos lidar com algo como disposição .


É importante lembrar que Observable é do tipo "frio", ou seja, nosso observável não "emite" nenhum evento até que ele seja inscrito. Observável existe até que ele envie uma mensagem de erro ou uma mensagem de erro ( concluída ). Se quisermos cancelar explicitamente a assinatura, podemos fazer o seguinte.


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

Existem mais linda opção correta.


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

Assim, adicionamos nossa assinatura ao saco de reciclagem ou ao DisposeBag .
Para que é isso? Se você, usando uma assinatura, não a adiciona à sua bolsa ou explicitamente não descarta , ou, em casos extremos, não conclui o observável de forma alguma, provavelmente haverá um vazamento de memória. Você usará o DisposeBag com muita frequência no seu trabalho com o RxSwift.


Operadores


A programação reativo-funcional (FRP abaixo) possui muitos operadores internos para transformar elementos observáveis. Existe um site rxmarbles , nele você pode ver o trabalho e o efeito de todos os operadores, bem, mas ainda olhamos para alguns deles.


Mapa


O operador de mapa é usado com muita frequência e acho que é familiar para muitos, com sua ajuda, transformamos todos os elementos recebidos.
Um exemplo:


 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) 

O que recebemos no console:


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

Pegamos cada elemento da sequência e criamos uma nova sequência resultante. Para deixar mais claro o que está acontecendo, é melhor escrever mais.


 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) 

O que é $ 0?


$ 0 é o nome do elemento por padrão, podemos usar registros abreviados e completos nos métodos, na maioria das vezes usamos registros abreviados.


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

Concorda que o formulário curto é muito mais simples, certo?


Filtro


O operador de filtro nos permite filtrar os dados emitidos por nosso observável, ou seja, ao assinar, não receberemos valores desnecessários para nós.
Um exemplo:


 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) 

O que recebemos no console?


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

Como vemos, no console, temos apenas os valores que satisfazem nossas condições.


A propósito, os operadores podem ser combinados; é assim que pareceríamos se quiséssemos aplicar o operador de filtragem e o operador de mapa imediatamente.


 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 

Distinto


Outro excelente operador associado à filtragem, o operador distinto permite ignorar apenas os dados alterados; é melhor recorrer imediatamente a um exemplo e tudo ficará claro.


 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) 

No console, temos o seguinte:


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

isto é, se o elemento atual da sequência for idêntico ao anterior, ele será ignorado e assim sucessivamente até que um elemento diferente do anterior apareça, é muito conveniente trabalhar com, por exemplo, a interface do usuário, ou seja, a tabela, caso recebamos dados são os mesmos que temos agora, então recarregue a tabela não deve ser.


Takelast


Um operador takeLast muito simples, pegamos o enésimo número de elementos do final.


 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) 

No console, temos o seguinte:


 next(6) completed 

Acelerador e Intervalo


Decidi contratar 2 operadores de uma só vez, simplesmente porque, com a ajuda do segundo, é fácil mostrar o trabalho do primeiro.


O operador do acelerador permite que você faça uma pausa entre capturar os valores transmitidos; é um exemplo muito simples: você escreve um aplicativo reativo, usa a barra de pesquisa e não deseja recarregar a tabela ou ir ao servidor toda vez que inserir dados; portanto, use o acelerador e diga que Deseja coletar dados do usuário a cada 2 segundos (por exemplo, você pode definir qualquer intervalo) e reduzir o consumo de recursos para processamento desnecessário, como funciona e é descrito no código? Veja abaixo um exemplo.


 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) 

No console estará:


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

O operador de intervalo faz com que o observável gere valores a cada 0,5 segundos em incrementos de 1 começando em 0, que é um cronômetro tão simples para Rx. Acontece que, uma vez que os valores são gerados a cada 0,5 segundos, dois valores são gerados por segundo, aritmética simples, e o operador do acelerador espera um segundo e assume o último valor.


Debounce


Debounce é muito semelhante à declaração anterior, mas um pouco mais inteligente na minha opinião. O operador de debounce aguarda a enésima quantidade de tempo e, se não houver alterações desde o início do cronômetro, ele assumirá o último valor; se enviarmos o valor, o cronômetro será reiniciado novamente. Isso é muito útil para a situação descrita no exemplo anterior, o usuário digita os dados, esperamos quando ele termina (se o usuário fica inativo por um segundo ou meio) e depois começamos a executar alguma ação. Portanto, se simplesmente mudarmos o operador no código anterior, não obteremos os valores no console, porque o debounce aguardará um segundo, mas a cada 0,5 segundos ele receberá um novo valor e reiniciará o temporizador, para que não obtenhamos nada. Vamos ver um exemplo.


 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) 

Nesta fase, proponho terminar com os operadores, existem muitos no framework RxSwift, não se pode dizer que todos eles são muito necessários na vida cotidiana, mas você ainda precisa saber sobre sua existência, por isso é aconselhável se familiarizar com a lista completa de operadores no site rxmarbles .


Programador


Um tópico muito importante que eu gostaria de abordar neste artigo é o agendador. Scheduler, permita-nos executar nosso observável em certos threads e eles têm suas próprias sutilezas. Para começar, existem 2 tipos de configuração do agendador observável - [observeOn] () e [subscribeOn] ().


Inscrever-se


SubscribeOn é responsável pelo encadeamento no qual todo o processo observável será executado até que seus eventos cheguem ao manipulador (assinante).


Observar


Como você pode imaginar, o observeOn é responsável por qual fluxo os eventos recebidos pelo assinante serão processados.


Isso é uma coisa muito legal, porque podemos facilmente colocar o download de algo da rede no fluxo em segundo plano e, quando obtivermos o resultado, ir para o fluxo principal e agir de alguma forma na interface do usuário.


Vamos ver como isso funciona com um exemplo:


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

No console, temos:


 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) 

Vemos que o observável foi criado no encadeamento em segundo plano e processamos os dados no encadeamento principal. Isso é útil ao trabalhar com uma rede, por exemplo:


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

Assim, a solicitação será executada no encadeamento em segundo plano e todo o processamento de resposta ocorrerá em principal. Nesta fase, é muito cedo para dizer que tipo de método rx o URLSession desenhou repentinamente; isso será discutido mais tarde; esse código foi dado como um exemplo do uso do Scheduler ; a propósito, obteremos a seguinte resposta ao 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} 

Na final, vamos ver que tipo de dados chegou até nós, para isso teremos que realizar uma verificação para não começar a analisar a mensagem concluída por acidente.


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

Verificamos que o evento não é uma mensagem de desligamento observável ou um erro dele, embora tenha sido possível implementar um método de assinatura diferente e processar todos esses tipos de eventos separadamente, mas você já pode fazer isso sozinho e obteremos o seguinte no 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} 

Dados recebidos :-)


Assuntos


Nos voltamos para o quente, ou seja, do observável "frio" ou "passivo" para o observável "quente" ou "ativo", que é chamado de subject'ami. Se antes disso, nosso observável começou seu trabalho somente depois de assiná-los e você teve uma pergunta em sua cabeça: "por que preciso de tudo isso?", Os sujeitos sempre trabalham e sempre enviam os dados recebidos.


Como é isso? No caso do observável, fomos à clínica, fomos à avó do mal em recepção até a recepção, eles se aproximaram e perguntaram para qual consultório deveríamos ir, então a granulação nos respondeu, no caso de sujeitos, a granulação fica e escuta a programação e as condições dos médicos no hospital e assim que recebe informações sobre a movimentação de qualquer médico, ele imediatamente diz isso, pergunta imediatamente Algo é inútil para granulação, podemos simplesmente aparecer, ouvi-la, sair, e ela continuará dizendo: algo é levado com comparações, vamos ao código.


Vamos criar um assunto e 2 assinantes, criar o primeiro logo após o assunto, enviar o valor para o assunto e, em seguida, criar o segundo e enviar mais alguns valores.


 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) 

O que veremos no console? certo, o primeiro conseguiu o primeiro evento, mas o segundo não.


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

Já é mais adequado à sua ideia de programação reativa?
Os assuntos são apresentados de várias formas; todos diferem na forma como enviam valores.


O PublishSubject é o mais simples, não importa para tudo, simplesmente envia a todos os assinantes o que eles descobriram e esquecem.


ReplaySubject - mas esse é o mais importante, ao criar, informamos o tamanho do buffer (quantos valores serão lembrados); como resultado, ele armazena os últimos n valores na memória e os envia imediatamente para o novo assinante.


 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) 

Nós olhamos para o 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ão é um absurdo como o anterior, tem um valor inicial e lembra o último valor e o envia imediatamente após a assinatura do assinante.


 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) 

Console


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

Conclusão


Este foi um artigo introdutório, escrito para que você saiba o básico e possa desenvolvê-lo posteriormente. Nos artigos a seguir, veremos como trabalhar com o RxSwift com componentes de interface do usuário do iOS, criando extensões para os componentes da interface do usuário.


Não RxSwift'om unido


A programação reativa é implementada não apenas na biblioteca RxSwift, existem várias implementações, aqui estão as mais populares delas: ReacktiveKit / Bond , ReactiveSwift / ReactiveCocoa . Todos eles têm pequenas diferenças de implementação, mas, na minha opinião, é melhor começar o seu conhecimento de reativismo com o RxSwift, pois ele é o mais popular entre eles e haverá mais respostas no ótimo Google , mas depois de você Para entender a essência desse conceito, você pode escolher as bibliotecas ao seu gosto e cor.
Autor do artigo: Pavel Grechikhin

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


All Articles