Historia del tema:
La abstracción de protocolos apareció en Swift como una alternativa a la herencia clásica de objetos de la OOP. Mientras que en Objective-C y otros lenguajes OOP predominaba el enfoque de herencia "de lo general a lo particular", Swift desde el principio promovió los protocolos como la principal forma de lograr la abstracción, enfatizando la composición sobre la herencia.
Problema:
La herencia clásica supone una jerarquía rígida: un árbol de subclases con una expansión obligatoria a través de override. Esto limita la flexibilidad, conduce a un código "frágil", un refactor complicado y a la hinchazón de las clases base. Además, Swift no soporta la herencia múltiple de clases, lo que significa que la reutilización de funcionalidad es posible solo a través de otros mecanismos.
Solución:
La abstracción de protocolos permite declarar un "conjunto de requisitos" que un tipo debe implementar. Los protocolos se pueden extender (extension) para introducir lógica común, lo que los acerca al concepto de "mixins":
Ejemplo de código:
protocol Drawable { func draw() } extension Drawable { func draw() { print("Dibujo predeterminado") } } struct Circle: Drawable {} let c = Circle() c.draw() // Imprimirá "Dibujo predeterminado"
Características clave:
¿Cuál es la diferencia entre el extension de un protocolo y la implementación normal en una clase?
La extensión de un protocolo a través de extension añade una implementación predeterminada solo para los casos en los que el usuario no ha implementado ese método en su tipo. Si el método es implementado explícitamente en el tipo, se llamará a ese.
Ejemplo:
protocol Demo { func foo() } extension Demo { func foo() { print("predeterminado") } } struct X: Demo { func foo() { print("personalizado") } } X().foo() // "personalizado"
¿Qué sucederá si se accede a un tipo que implementa un protocolo y su extensión como si fueran datos del protocolo?
Si un protocolo declara un método como obligatorio (requisito), se utiliza la implementación del tipo específico incluso al convertir al tipo del protocolo. Sin embargo, si se añade una nueva propiedad (no declarada anteriormente en el protocolo) en la extensión, esta solo estará disponible a través de la extensión, no a través del tipo del protocolo.
¿Se pueden almacenar en un arreglo instancias de diferentes structs que implementan el mismo protocolo?
Sí — gracias a los tipos "existenciales" (por ejemplo, [Drawable]) es posible almacenar colecciones heterogéneas:
struct Tri: Drawable { func draw() { print("Triángulo") } } let arr: [Drawable] = [Circle(), Tri()] arr.forEach { $0.draw() }
En la empresa había una superclase base Shape, de la cual heredaban todas las figuras (Circle, Square, Polygon). La clase base crecía porque cada nueva figura tenía que ser soportada a través de override. Ampliar el sistema se volvía cada vez más difícil — cada nuevo tipo rompía el ABI y forzaba a reescribir el código existente.
Ventajas:
Desventajas:
Se empezaron a utilizar varios protocolos: Drawable, Colorable, Animatable. Ahora cada figura se puede hacer simultáneamente "animable y colorida", sin modificar las otras estructuras. Funcionalidades nuevas se añaden a través de extensiones.
Ventajas:
Desventajas: