ProgramaciónDesarrollador iOS

Explique cómo funcionan las abstracciones de protocolos en Swift y en qué se diferencian de la herencia de clases. ¿Cuándo se deben usar protocolos en lugar de una jerarquía de clases?

Supere entrevistas con el asistente de IA Hintsage

Respuesta.

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:

  • Los protocolos soportan composición múltiple (requisitos múltiples).
  • No crean una jerarquía rígida — es más fácil extender, no rompen la arquitectura.
  • Pueden trabajar tanto con tipos de valor (struct/enum) como con clases, lo cual no es posible con la herencia.

Preguntas capciosas.

¿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() }

Errores comunes y anti-patrones

  • Heredar de una superclase grande por el bien de una interfaz común en lugar de dividir en protocolos
  • Uso excesivo de extension sin requisitos explícitos en los protocolos
  • Intentar almacenar un protocolo con associatedtype en una colección ([SomeProtocol]) — esto no es soportado

Ejemplo de la vida real

Caso negativo

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:

  • Implementación rápida de nuevos métodos comunes a través de la clase base

Desventajas:

  • Jerarquía monolítica con dependencias excesivas
  • Mala reutilización fuera de la jerarquía
  • Conflictos de métodos override

Caso positivo

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:

  • Flexibilidad, fácil mantenimiento y ampliación
  • Mejora en la reutilización en diferentes contextos

Desventajas:

  • Requiere un diseño cuidadoso de la API y conocimiento de associatedtype