Introducción a la programación orientada a protocolos con Swift

Introducción

La programación orientada a objetos (OOP) se ha venido usando desde hace décadas y se ha convertido casi en el estándar a la hora de construir grandes proyectos de software. Centrándonos en iOS, la OOP es el eje central tanto del sistema como de los frameworks que lo conforman haciendo que sea prácticamente imposible hacer una app iOS sin utilizarla.

La OOP tiene muchas ventajas pero también viene con algunos problemas, como por ejemplo:

  • Las clases son tipos de referencia, por lo que cuando se trabaja en entornos multihilo puede llegar a causar comportamientos inesperados.
  • El problema de la herencia multiple, la cual no soportan la mayoría de de los lenguajes de programación y que añade una gran complejidad al código
  • Debido a un gran acoplamiento entre las clases es más difícil realizar test unitarios de una única clase

Swift trata, desde su propia concepción, de combatir los problemas de la OOP, de hecho, Apple asegura que es el primer lenguaje orientado a protocolos y que en su interior se aplica esta técnica a menudo.

Desde los inicios de Swift se ha abrazado la idea de los tipos por valor como los Struct y los Enums. Estos son tratados como ciudadanos de primera clase y pueden poseer propiedades, métodos, extensiones, etc que son cosas mas propias de clases en otros lenguajes.

En la versión 2 del lenguaje, se introdujo una serie de nuevas características como la extensión de protocolos y la posibilidad de una implementación por defecto.

Manos a la Obra

A continuación vamos a mostrar cómo usar los protocolos en lugar de clases con unos simples ejemplos

Para empezar vamos a crear dos simples protocolos

protocol Vehiculo {
          var marca: String { get set }
          var color: String { get set }
          var velocidadMax: Int { get set }
}


protocol MarchaAtras {
          var velocidadMarchaAtras: Int { get set }
}

El primero representará un vehículo y el segundo la posibilidad de ir marcha atrás. Como puede verse en el protocolo solamente se define la existencia de ciertas cosas.

Una vez hemos creado los protocolos, vamos a crear una Moto que adopta el protocolo Vehículo

struct Moto: Vehiculo {
         var marca = "Yamaha"
         var color = "Rojo"
         var velocidadMax = 240
}

Ahora crearemos un Coche que adopta ambos protocolos previamente creados.

struct Coche: Vehiculo, MarchaAtras {
         var marca: "Renault"
         var color: "Azul"
         var velocidadMax = 260
         var velocidadMarchaAtras = 30 
}

Es posible no sólo que un struct adopte un protocolo, también existe la herencia entre protocolos pudiendo hacerse

protocol Protocolo1: Protocolo2, Protocolo3 {
         var marca: String { get set }
         var color: String { get set }
         var velocidadMax: Int { get set }
}

Los protocolos no solo pueden ser adoptados por structs u otros protocolos, mediante la siguiente definición podemos asegurarnos de que un protocolo solamente sea adoptado por una clase por ejemplo

protocol ProtocoloX: class {
        //Codigo
}

 

A partir de Swift 2 es donde viene el poder real de los protocolos con la posibilidad de extender el protocolo. Esto no solo implica añadir una definición de una función sino que permite darle una implementación por defecto. Por ejemplo imaginemos que queremos comparar si un vehículo es más rápido que otro basándonos en su velocidad máxima. Todo lo que hay que hacer es añadir una extension al protocolo Vehiculo

extension Vehiculo {
         func isMasRapidoQue(item: Vehicle) -> Bool {
                 return self.velocidadMax > item.velocidadMax
          }
}

let megane = Coche()
let m1 = Moto()

m1.isMasRapidoQue(megane)

Ahora vamos a definir otra extensión del protocolo Vehiculo pero esta vez solo se aplicará para los tipos que conformen también el protocolo MarchaAtras. Esta función compara quien tiene el rango de velocidad más amplio.

extension Vehiculo where Self: MarchaAtras {
        func tieneMasRangoQue(item: Self) -> Bool {
                   return (self.velocidadMax + self.velocidadMarchaAtras) > 
                              (item.velocidadMax + item.velocidadMarchaAtras)
        }
 }

let panda = Coche(marca: "Seat", color: "Rojo", velocidadMax: 240, velocidadMarchaAtras: 20)

let laFerrari = Coche(marca: "Ferrari", color: "Rojo", velocidadMax: 340, velocidadMarchaAtras: 20)

laFerrari.tieneMasRangoQue(item: panda)

La palabra Self con S mayúscula es usada en este caso para representar la clase o estructura que conforma el protocolo. En el ejemplo de arriba, el Self representa la estructura Coche.

No es la panacea

La programación orientada a protocolos en Swift viene también con sus desventajas:

  • La ventaja de la herencia en la OOP es la reutilización de código en las clases que heredan de la superclase. Con la extensión de protocolos solamente se puede sustituir la implementación por defecto por una propia, no se puede extender o ampliar la implementación por defecto con lo que la posibilidad de reutilizar el código desaparece.
  • Es casi imposible saber quién o cómo esta conformando un protocolo, con lo que si se añaden nuevas funciones al protocolo es posible que se produzcan inconsistencias en alguna parte de la aplicación.

Para terminar

La intención de esta entrada es ser una breve introducción a los protocolos y a la programación orientada a protocolos, apoyada en ejemplos

¿A partir de aquí?

Apple cuenta con dos fantásticas charlas sobre la POP, una en la WWDC 2015 y otra en la WWDC 2016

Si quieres profundizar en este tema te recomiendo tutoriales más avanzados como este de Ray Wenderlich en el que se da una explicación bastante amplia sobre este tema

Introducing Protocol-Oriented Programming in Swift 3

 

Otros artículos de interés:

Atractivas animaciones para la intro de tu app

Cómo refirmar una aplicación iOS (Nivel Básico)

Deja un comentario

¿Necesitas una estimación?

Calcula ahora