
Buen dia, Habrovsk. En esta serie de artículos, me gustaría hablar sobre programación reactiva, es decir, el marco
RxSwift . Hubo artículos sobre RxSwift en Habré y en la red, pero, en mi opinión, son demasiado difíciles para los principiantes. Por lo tanto, si comienzas a entender la programación reactiva en iOS, entonces te pido cat.
Comencemos definiendo qué es la programación reactiva.
La programación reactiva es un paradigma de programación centrado en los flujos de datos y la difusión del cambio.
Eso es lo que nos dice la gran Wikipedia .
En otras palabras, en el caso de que programemos en un estilo imperativo, escribimos en el código un conjunto de comandos que deben ejecutarse secuencialmente. El estilo de programación reactiva se adhiere a varios otros conceptos. Con un estilo de programación reactivo, nuestro programa es un "oyente" de los cambios de estado en nuestros objetos observados. Suena complicado, pero no lo es, es suficiente para penetrar en este concepto y todo será extremadamente fácil y claro, sin errores todavía .
No pintaré cómo instalar el marco, es fácil hacer clic en el enlace . Vamos a practicar.
Observable
Comencemos con un simple pero importante observable u observable. Observable es lo que nos dará los datos, es necesario para generar un flujo de datos.
let observable = Observable<String>.just(" observable")
BINGO ! Creamos el primer observable.

Como creamos el objeto observado, es lógico que necesitemos crear un objeto que observe.
let observable = Observable<String>.just(" observable") _ = observable.subscribe { (event) in print(event) }
obtenemos lo siguiente en el registro:
next( observable) completed

Observable nos envía información sobre sus eventos, solo hay 3 tipos:
Junto con el siguiente elemento viene el elemento que enviamos y todos los eventos enviados por nosotros, el error se envía como su nombre lo indica en caso de error, y se completa en el caso en que nuestro observable ha enviado todos los datos y finaliza.
Podemos crear más detallado observador suscríbete y obtén una vista más conveniente para manejar todos los eventos.
_ = observable.subscribe(onNext: { (event) in print(event) }, onError: { (error) in print(error) }, onCompleted: { print("finish") }) { print("disposed")
finish disposed
En observable, puede crear una secuencia no solo a partir de una línea, y de hecho no solo a partir de líneas, podemos poner cualquier tipo de datos allí.
let sequence = Observable<Int>.of(1, 2, 4, 5, 6) _ = sequence.subscribe { (event) in print(event) }
next(1) next(2) ... completed
Observable se puede crear a partir de una 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
Un observable puede tener cualquier número de suscriptores . Ahora terminología, ¿qué es un observable?
Observable es la base de todo Rx, que genera asincrónicamente una secuencia de datos inmutables y permite que otros se suscriban a ella.
Desechando
Ahora que sabemos cómo crear una secuencia y suscribirnos a ellos, debemos ocuparnos de algo así como la eliminación .
Es importante recordar que Observable es un tipo "frío", es decir, nuestro observable no "emite" ningún evento hasta que se suscribe. El observable existe hasta que envía un mensaje de error o un mensaje de error ( completado ). Si queremos cancelar explícitamente la suscripción, podemos hacer lo siguiente.
// №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()
Hay mas hermosa Opción correcta.
// "" 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)
Por lo tanto, agregamos nuestra suscripción a la bolsa de reciclaje o al DisposeBag .
¿Para qué es esto? Si usted, utilizando una suscripción, no lo agrega a su bolsa o no llama explícitamente a desechar , o, en casos extremos, no completa de manera observable , entonces lo más probable es que tenga una pérdida de memoria. Utilizará DisposeBag muy a menudo en su trabajo con RxSwift.
Operadores
La programación funcional-reactiva (FRP a continuación) tiene muchos operadores incorporados para transformar elementos observables. Hay un sitio rxmarbles , en él puedes ver el trabajo y el efecto de todos los operadores, bueno, pero aún miramos algunos de ellos.
Mapa
El operador del mapa se usa con mucha frecuencia y creo que es familiar para muchos, con su ayuda transformamos todos los elementos recibidos.
Un ejemplo:
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é obtenemos en la consola?
next(2) next(4) next(6) completed
Tomamos cada elemento de la secuencia y creamos una nueva secuencia resultante. Para aclarar lo que está sucediendo, es mejor escribir más.
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é es $ 0?
$ 0 es el nombre del elemento por defecto, podemos usar registros abreviados y completos en los métodos, la mayoría de las veces usamos abreviaturas.
// let transformObservable = observable.map { $0 * 2 } // let transformObservable = observable.map { (element) -> Int in return element * 2 }
De acuerdo en que la forma abreviada es mucho más simple, ¿verdad?
Filtro
El operador de filtro nos permite filtrar los datos emitidos por nuestros observables, es decir, al suscribirse, no recibiremos valores innecesarios para nosotros.
Un ejemplo:
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é obtenemos en la consola?
next(3) next(4) next(5) ... completed
Como vemos, en la consola solo tenemos aquellos valores que satisfacen nuestras condiciones.
Por cierto, los operadores se pueden combinar, así es como se vería si quisiéramos aplicar tanto el operador de filtrado como el operador de mapa de inmediato.
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)
Consola:
next(6) next(8) next(10) next(12) next(14) completed
Distinto
Otro operador excelente que está asociado con el filtrado, el operador discreto le permite omitir solo los datos modificados, lo mejor es recurrir inmediatamente a un ejemplo y todo quedará 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)
En la consola, obtenemos lo siguiente:
next(1) next(2) next(3) next(5) next(6) completed
es decir, si el elemento actual de la secuencia es idéntico al anterior, se omite y así sucesivamente hasta que aparezca un elemento diferente del anterior, es muy conveniente cuando se trabaja con, por ejemplo, la IU, es decir, la tabla, en caso de que recibamos datos son los mismos que tenemos ahora, luego recargar la tabla no debería ser.
Takelast
Un operador takeLast muy simple, tomamos el enésimo número de elementos desde el 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)
En la consola obtenemos lo siguiente:
next(6) completed
Acelerador e intervalo
Luego decidí tomar 2 operadores a la vez, simplemente porque con la ayuda del segundo, es fácil mostrar el trabajo del primero.
El operador del acelerador le permite tomar un descanso entre la captura de los valores transmitidos, es un ejemplo muy simple, escribe una aplicación reactiva, usa la barra de búsqueda y no quiere recargar la tabla o ir al servidor cada vez que ingresa datos, por lo que usa el acelerador y por lo tanto dice que ¿Desea tomar datos del usuario cada 2 segundos (por ejemplo, puede establecer cualquier intervalo) y reducir el consumo de recursos para el procesamiento innecesario, ¿cómo funciona y se describe en el código? Vea a continuación un ejemplo.
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)
En la consola estará:
throttle next(0) throttle next(2) throttle next(4) throttle next(6) ...
El operador de intervalo hace que el observable genere valores cada 0,5 segundos en incrementos de 1 a partir de 0, que es un temporizador tan simple para Rx. Resulta que una vez que se generan los valores cada 0,5 segundos, se generan 2 valores por segundo, aritmética simple, y el operador del acelerador espera un segundo y toma el último valor.
Debounce
Debounce es muy similar a la declaración anterior, pero un poco más inteligente en mi opinión. El operador antirrebote espera la enésima cantidad de tiempo, y si no hay cambios desde el inicio de su temporizador, toma el último valor, si enviamos el valor, el temporizador se reiniciará nuevamente. Esto es muy útil para la situación descrita en el ejemplo anterior, el usuario ingresa datos, esperamos cuando finaliza (si el usuario está inactivo durante un segundo o medio), y luego comenzamos a realizar alguna acción. Por lo tanto, si simplemente cambiamos el operador en el código anterior, entonces no obtendremos los valores en la consola, porque debounce esperará un segundo, pero cada 0.5 segundos recibirá un nuevo valor y reiniciará su temporizador, por lo que no obtendremos nada. Veamos un ejemplo.
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)
En esta etapa, propongo terminar con los operadores, hay muchos de ellos en el marco RxSwift, no se puede decir que todos son muy necesarios en la vida cotidiana, pero aún necesita saber acerca de su existencia, por lo que es recomendable familiarizarse con la lista completa de operadores en el sitio web rxmarbles .
Programador
Un tema muy importante que me gustaría abordar en este artículo es el programador. Programador, nos permite ejecutar nuestro observable en ciertos hilos y tienen sus propias sutilezas. Para comenzar, hay 2 tipos de configuración del planificador observable: [observeOn] () y [subscribeOn] ().
Suscribirse
SubscribeOn es responsable del hilo en el que se ejecutará todo el proceso observable hasta que sus eventos lleguen al controlador (suscriptor).
Observeon
Como puede suponer, observeOn es responsable de en qué flujo se procesarán los eventos recibidos por el suscriptor.
Esto es algo muy bueno, porque podemos poner fácilmente la descarga de algo de la red en la secuencia de fondo y, cuando obtengamos el resultado, ir a la secuencia principal y actuar de alguna manera en la interfaz de usuario.
Veamos cómo funciona esto con un ejemplo:
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) })
En la consola obtenemos:
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 el observable se creó en el hilo de fondo, y procesamos los datos en el hilo principal. Esto es útil cuando se trabaja con una red, por ejemplo:
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)") }
Por lo tanto, la solicitud se ejecutará en el subproceso en segundo plano y todo el procesamiento de respuesta se realizará en main. En esta etapa, es demasiado pronto para decir qué tipo de método rx URLSession dibujó de repente, esto se discutirá más adelante, este código se dio como un ejemplo de uso de Scheduler , por cierto, obtendremos la siguiente respuesta a la consola.
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}
En la final, veamos qué tipo de datos nos llegaron, para esto tendremos que realizar una verificación para no comenzar a analizar el mensaje completo por accidente.
_ = 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 el evento no sea un mensaje de apagado observable o un error que surgió de él, aunque fue posible implementar un método de suscripción diferente y procesar todos estos tipos de eventos por separado, pero ya puede hacerlo usted mismo y obtendremos lo siguiente en la consola.
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}
Datos recibidos :-)
Sujetos
Pasamos a lo caliente, es decir, de lo observable "frío" o "pasivo" a lo observable "caliente" o "activo", que se denominan sujetos'ami. Si antes de eso, nuestro observable comenzó su trabajo solo después de suscribirse a ellos y tenía una pregunta en su cabeza, "¿por qué necesito todo esto?", Entonces los Sujetos siempre trabajan y siempre envían los datos recibidos.
Como es eso En el caso de observable, fuimos a la clínica, fuimos a la abuela malvada en recepcion a la recepción, se acercaron y preguntaron a qué consultorio deberíamos ir, luego la granulación nos respondió, en el caso de los sujetos, la granulación se para y escucha el horario y la condición de los médicos en el hospital y tan pronto como recibe información sobre el movimiento de cualquier médico, él inmediatamente responde esto, pregunte Algo es inútil para la granulación, podemos simplemente acercarnos, escucharla, irnos, y ella continuará diciendo, algo se deja llevar por las comparaciones, vamos al código.
Creemos un sujeto y 2 suscriptores, creemos el primero justo después del sujeto, enviemos el valor al sujeto y luego creemos el segundo y enviemos un par de valores más.
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)
¿Qué veremos en la consola? bien, el primero logró obtener el primer evento, pero el segundo no.
next(1) next(2) next(2) next(3) next(3) next(4) next(4)
¿Ya está más adaptado a su idea de programación reactiva?
Los sujetos vienen en varias formas; todos difieren en cómo envían valores.
PublishSubject es el más simple, no le importa a todos, simplemente envía a todos los suscriptores lo que ha recibido y lo olvida.
ReplaySubject, pero esto es lo más importante, al crear, le decimos el tamaño del búfer (cuántos valores se recordarán), como resultado, almacena los últimos n valores en la memoria y los envía inmediatamente al nuevo suscriptor.
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)
Miramos la consola
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 no es tan absurdo como el anterior, tiene un valor inicial y recuerda el último valor y lo envía inmediatamente después de que el suscriptor se suscribe.
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)
Consola
next(0) next(1) next(1) next(2) next(2) next(3) next(3) next(3) next(4) next(4) next(4)
Conclusión
Este fue un artículo introductorio escrito para que conozcas los conceptos básicos y puedas desarrollarlos posteriormente. En los siguientes artículos, veremos cómo trabajar con RxSwift con componentes de IU de iOS, creando extensiones para componentes de IU.
No RxSwift'om unidos
La programación reactiva se implementa no solo en la biblioteca RxSwift, hay varias implementaciones, aquí están las más populares: ReacktiveKit / Bond , ReactiveSwift / ReactiveCocoa . Todos tienen pequeñas diferencias en la implementación bajo el capó, pero en mi opinión, es mejor comenzar su conocimiento del reactivismo con RxSwift, ya que es el más popular entre ellos y habrá más respuestas en Google , pero después de usted Para comprender la esencia de este concepto, puede elegir bibliotecas a su gusto y color.
Autor del artículo: Pavel Grechikhin