Saltar al contenido

Hemos sido dos compañeros del Centro de Movilidad (Ali Ghanbari y yo) los que nos hemos peleado en un proyecto nuevo con este sistema. Y, gracias a ello, puedo explicarlo de forma (más o menos) sencilla para que alguien que no tenga conocimientos, pueda tener una ligera idea de cómo implementar “Rx” en un proyecto.

¿Qué es la programación reactiva?

Los sistemas desarrollados como Sistemas Reactivos son más flexibles, están poco acoplados y son escalables.

La programación reactiva está orientada a la reacción, al flujo de datos y al principio de causalidad, es decir, cada causa esta asociada a sus efectos. Se entenderá mejor con los ejemplos expuestos más abajo.


Programación reactiva en Swift

RxSwift se basa en ReactiveX, que implementa el diseño reactivo basándose en extender el patrón observer (patrón observador). RxSwift se ha creado para una mejor integración en sistemas operativos iOS/macOS.


Observables

Los observables son “escuchadores” de eventos.

La forma de utilizarlos es prácticamente idéntica a otros lenguajes.

Gracias a otro compañero del CDM (@Carlos Mateo Benito), que hizo Rx en Kotlin, pude entender bien todo el tema de observables. 

Los observables no solo se utilizan en Rx. La lógica de los observables es idéntica a los observers de Swift. Por ejemplo en el NotificationCenter de Swift

(NotificationCenter.default.addObserver(self, selector: #selector(playbackDidStart), name: .playbackStarted, object: nil))

 Los observables escuchan eventos para enviar una respuesta al “suscriptor” del evento.

Un observable tiene tres “fases”:

1. onNext

2. onComplete

3. onError 

Un observable puede escuchar, por ejemplo, a un evento de Bluetooth, el cual podría estar enviando notificaciones constantemente, hasta que se termine la conexión o cuando considere el programador. Entonces el observable estaría escuchando cada notificación que manda el Bluetooth, por lo que en cada recepción del evento, pasaría por el evento “onNext()”, del observable. Si se terminase la conexión Bluetooth, a lo mejor desencadena un error, por lo que entraría por el “onError()”. Y si el dispositivo Bluetooth ha terminado de recibir datos, y queremos que deje de escuchar el observable, se llamaría al “onComplete()”.

Los métodos “onComplete” y “onError” liberan el observable (de memoria) para que deje de escuchar. Si nunca se llaman, el observable estará escuchando siempre hasta que se libere.

Si tenemos una llamada a un WS que únicamente recibimos una respuesta, pues podríamos seguir la misma forma, solo que únicamente pasará una vez por el “onNext” y después habrá que llamar al “onComplete” si no se ha producido ningún error.
 

Observable<String>.create { observer in
   /// Customize observable
   observer.onNext("1")
   observer.onNext("?")
   observer.onComplete()

   return Disposables.create()
   }
   .subscribe(
     onNext: { print($0) },
     onError: { print($0) },
     onCompleted: { print("Completed") },
     onDisposed: { print("Disposed") }
   )
   .disposed(by: disposeBag)


Si no hubiera ningún objeto suscrito a ese observable (Observable frío), entonces no se ejecutaría ningún código, ya que ninguna vista lo está escuchando.

Si fuera un Observable caliente, podría emitir eventos incluso aunque no haya nadie suscrito. Y si una vista se suscribe, entonces le empezaría a llegar los nuevos eventos del observable.

Un ejemplo de Observable frío son los que usamos con Observable.create, .just, etc… Y un ejemplo de Observable caliente son los eventos de la view, es decir: UIButton.rx.tap o UITextField.rx.text


En qué “capas” utilizar Rx

 Hay dos niveles donde se podría utilizar Rx.

  1. La primera y mas común es a nivel de obtener datos de un servidor. Si tenemos clean architecture en nuestro proyecto, sería de la capa Domain a Data. Utilizarlo aquí, nos facilita muchísimo el uso de hilos, ya que todo proceso de pedir datos, debería ir siempre en un hilo secundario, y el principal dejarlo solo para la interfaz. Por lo que si nuestro proyecto tiene Use cases/Interactors, bastaría con implementar un BaseUsecase, que gestione todo el tema de hilos mediante observables.

  2. La segunda es a nivel de vista, en la que un objeto de la vista, por ejemplo, un textfield puede estar suscrito a eventos de cambio del texto en el input, para que cambie el color del background de la view. Aquí siempre se utilizará el hilo principal, ya que es necesario para actualizar datos de la vista. Para hacer Rx en la interfaz, si no quieres implementar observables, existen los objetos llamados Drivers, que han sido diseñados específicamente para trabajar con Rx a nivel de vista, ya que siempre trabajan en el hilo principal. Son los mas recomendados para trabajar en la interfaz, ya que evitan muchos posibles errores del programador.


Ventajas e inconvenientes

Ventajas:

  • Gestión de hilos súper sencilla.
  • Fácil composición y reusabilidad.
  • Fácil manipulación de datos.
  • ​Gestión de errores (Por defecto los frameworks reactivos dan la opción de reintentar la operación fuente del stream en el caso de fallo).
 
return manager.rx

           .responseString(.post, urlString, parameters: parameters)

           .retry(2)

           .flatMap { dataString -> Observable<String> in

               return Observable.just(dataString)

           }

           .catchError { Observable.error($0) }

 

Inconvenientes:

  • Multitud de formas de obtener y tratar el resultado de un observable (flatMapFirst, flatMapLatest, compatMap, map, zip, withLatestFrom, etc.).​
  • A nivel de vista, es muy sencillo hacer un “retain cycle”, ya que normalmente en la vista, mencionamos al “self” para refrescar un Label o cualquier propiedad. Por suerte esto tiene solución, que es poner [weak self] dentro de lo que queramos hacer, pero hay que tener mucho cuidado.
 
tableView.rx.itemSelected.asDriver()

    .drive(onNext: { [weak self] indexPath in

        if let cell = self?.tableView.cellForRow(at: indexPath) as? InsuranceCell {

            if !cell.isExpanded {

                cell.expand()

                self?.updateHeightTableView(adding: cell.getViewHeight ())

            } else {

                cell.collapse()

                self?.updateHeightTableView(adding: -cell.getViewHeight())

             }

        }

    })

    .disposed(by: disposeBag)
 Jorge Enrique Guilabert
Jorge Enrique Guilabert

Programador de Desarrollo Mobile iOS/Android nativo en BABEL.

logo linkedin compartir en Linkedin Contacto

Otros artículos destacados