En el artículo de hoy me gustaría dar una pequeña introducción a Combine y mostrar sus diferencias con RxSwift, y la mejor manera de empezar es mostrando la definición de Combine proporcionada por Apple:

A unified, declarative API for processing values overtime

Seguro que te suena familiar, y es que como ya hemos avanzado, en este artículo vamos a hablar de algunas características y diferencias que comparten Combine y RxSwift, empezando por Combine y sus características principales.

Combine vs RxSwift: Introducción a Combine

Lo primero a destacar son sus tres cualidades

  • Generic.
  • Type safe.
  • Composition first.

Apple nos cuenta en su keynote sobre Combine que los conceptos principales son sencillos y fáciles de entender pero combinados unos con otros se pueden hacer cosas más complejas e interesantes.

Dentro de combine encontraremos:

  • Publishers
  • Subscribers
  • Operators

Publishers

Son la parte más declarativa de la API de Combine. Definen cómo son producidos los valores o los errores.
Son Value type, en Swift, Structs.
Los Publishers permiten la subscripción de Subscribers, esto nos permitirá recibir los valores cuando estos se emitan.

protocol Publishers {
    associatedtype Output
    associatedtype Failure: Error

    func subscribe<S: Subscriber>(_ subscriber: S) 
        where S.input == Output, S.Failure == Failure
}

Subscribers

Los subscribers son la otra cara de la moneda de los publishers, estos son los que reciben valores por el stream, y dado que estos valores pueden acabar mutando son de tipo Reference type como las clases.

protocol Subscriber {
    associatedtype Input
    associatedtype Failure: Error

    func receive(subscription: Subscription)
    func receive(_ input: Input) -> Subscribers.Demand
    func receive(completion: Subscribers.Completion<Failure>)

}

Me recuerda a algo…

Con estos conceptos básicos de Combine podemos deducir que es muy parecido a otros frameworks ya existentes de programación reactiva como RxSwift o ReactiveCocoa. Para este artículo voy a centrarme en la comparación con RxSwift.

Si estás familiarizado en RxSwift puedes ver que los Publishers son los Observables y los Subscribers los Observers, tienen diferentes nombres pero ambos funcionan con la misma mecánica.
Un Publisher expone valores que pueden cambiar y un Subscriber se suscribe para recibir todos los cambios.

Combine vs RxSwift: Diferencias

Compatibilidad actual

Para empezar, Combine no dispone de backward compatibility, es decir, no está disponible en sistemas anteriores a iOS 13 / macOS Catalina dado que necesita el nuevo proceso de runtime introducido para funcionar, y tampoco hay planes futuros de proporcionar esta compatibilidad a sistemas anteriores.

Por el contrario RxSwift funciona en iOS 8 y posteriores.

Gestión de errores

Si nos fijamos en la especificación del protocolo de Observable veremos las primeras diferencias.

En Combine cada Publisher necesita especificar un error type, mientras que en RxSwift Observables no usan un error type, pueden lanzar (throw) cualquier tipo de error en cualquier momento. Esto hace que en RxSwift los Observables sean más fáciles de usar, dado que no tienes que pensar qué tipos de error deben ser lanzados. Sin embargo esto significa que debes tener cuidado de gestionar los errores por tu cuenta, el compilador no te ayudará si te olvidas de alguno, con Combine al ser más estricto esto no sucedería.

En Combine si tu stream no lanza errores de ningún tipo puedes marcarlo como tipo Never.

Podríamos entender esta especificación explícita de los tipos de error de Combine como una capa más de seguridad pero que añade complicación al código.
En RxSwift se puede lograr algo similar usando el tipo Result, este añade un tipo de error adicional pero tu stream no pararía después de lanzarse un error, o sino usando un stream específico para la gestión de errores.

Sobre la gestión de errores hay una diferencia más, Combine separa las funciones como throwing o non-throwing. Por ejemplo, hay operadores que tienen una versión que pueden lanzar errores y la versión sin el throw.

Performance

En cuanto a performance no podemos negar que RxSwift usado correctamente es un framework muy optimizado, pero Combine ha sido construido por los ingenieros de Apple fijándose 100% en su performance.

Tal y como podemos ver en el blog de Flawless iOS han hecho una comparativa ejecutando dos bloques de código que realizan el mismo trabajo, uno en Combine y otro en RxSwift y podemos ver que la performance en tiempo de Combine resulta ganadora.

Alt Image Text

Uno de los principales motivos de esta mejora es debido a que RxSwift usa Swift como su lenguaje principal y necesita hacer muchos sinks en las capas de bajo nivel del framework, esto afecta a su rendimiento. En el otro lado Combine es un proyecto de código cerrado, y puede no estar desarrollado necesariamente en Swift pero sí exponer una interfaz pública en Swift. Apple puede usar muchas optimizaciones de rendimiento que no están disponibles para los programadores fuera de la empresa.

Operadores

Combine y RxSwift tienen muchos operadores que realizan el mismo trabajo o muy similar, sin embargo el naming es diferente. En muchos casos en Combine hay operadores con nombre diferentes que en RxSwift pero que tienen su equivalente en cuanto a funcionalidad.

Por suerte la tabla de guía creada por Shai Mishali nos puede ayudar a relacionar todos esos operadores con namings diferentes.

Open source

Otro hecho importante es que Combine no es open source, y es bastante lógico si nos fijamos en todos los otros frameworks de Apple, los cuales no son open source. Como hemos comentado, Combine es muy posible que use features del propio sistema, que no están disponibles para todos. Del mismo modo que aunque fuera open source ellos saben más que nadie cómo lidiar con los problemas que puedan surgir si están relacionados con su core.

DisposeBag

Seguramente ya estáis familiarizados con el patrón de gestión de memoria de RxSwift, DisposeBag. En lugar de guardar cada subscripción separadamente y finalizar con todas ellas cuando el controller o class se desinicializa, simplemente escribimos .disposed(by: disposeBag) y el framework se encargará de detectar el deinit y deshacerse de todas esas dependencias de Rx.

No hay nada similar a DisposeBag en Combine.

UI Framework

Dentro del framework reactivo necesitamos algún modo de vincular los flujos reactivos a la vista y vice versa. RxCocoa es la solución para RxSwift.

Combine no dispone de un framework concreto para realizar estos binds, disponemos del método assign para vincular un stream a un key path y a una propiedad de la vista.

publisher.assign(to: \.text, on: label)

No hay ningún modo (aún) en Combine de obtener un stream de un componente de UI, de modo que si podemos hacerlo con RxCocoa

Conclusión

Mi opinión dejando de lado las diferencias entre ambos frameworks, es que es muy positivo para Swift que aparezcan más herramientas de programación reactiva. Es muy posible que la aparición de Combine proporcione a RxSwift más popularidad, y en los próximos años con la madurez que irá adquiriendo Combine, quizá en algún punto determinado valdrá la pena saltar a este framework de Apple. Por ahora, y dado que, como hemos comentado, no tiene compatibilidad con versiones anteriores, podemos seguir disfrutando de RxSwift.