Protocolos Genéricos con Tipo Asociado
Aprende cómo crear Protocolos Genéricos y Tipos Asociados
Introducción
Ya hemos aprendido a usar Protocolos y sé que te has vuelto adicto a ellos. Ahora vamos a crear Protocolos Genericos con Tipos Asociados y utilizar la palabra reservada where
para Restricciones de Tipo, similar a cómo se hace con los Generics. Empecemos!
Requisitos
Este tutorial es para un nivel intermedio/avanzado. Por lo tanto, espero que estés familiarizado con los temas a continuación:
- Introducción a Genérics.
- Introducción a la Programación Orientada a Protocolo.
- Closures
- Typealias.
Si no estas familiarizado con estos temas te recomiendo el Curso de Bob.
La regla principal
El lenguaje de programación Swift se considera Type Safe (de tipado seguro). Esto significa que los Tipos de Datos deben ser definidos antes de ser compilados.
Recordatorio
Antes de sumergirnos en Protocolos Genéricos, debes familiarizarte con el código siguiente:
struct EstructuraGenerica<T> {
var propiedad: T?
}
Podrías declarar explícitamente el tipo de T o dejar que Swift deduzca basado en el valor:
let estructuraExplicita = EstructuraGenerica<Bool>()
// T es Bool
let estructuraImplicita = EstructuraGenerica(propiedad: "Bob")
// T es String
Ten en cuenta el principio de que todo Tipo debe ser definido.
Protocolo normal
Antes de apreciar los Protocolos Genéricos, echemos un vistazo a como lo haciamos antes. Vamos a crear un protocolo que requiere agregar una propiedad cuyo tipo sea String:
protocol ProtocoloNormal {
var propiedad: String { get set }
}
Creamos una clase que conforme el Protocolo:
class ClaseNormal: ProtocoloNormal {
var propiedad: String = "Bob"
}
Suena bien. Sin embargo, ProtocoloNormal
obliga a ClaseNormal
a trabajar con String. Pero, ¿qué pasa si quieres una propiedad Int o Bool?
Es hora de introducir los Tipos Asociados en Protocolos.
Introducción a los Tipos Asociados
En Protocolos Genéricos, para crear algo como
protocol ProtocoloGenerico {
associatedtype miTipo
var miPropiedad: miTipo { get set }
}
Associated type = type alias + generics
Ahora, cualquier cosa que se conforme a ProtocoloGenerico
debe implementar miPropiedad
. Sin embargo, el Tipo no está definido. Por lo tanto, la clase o estructura que se conforma con el protocolo debe definirlo implícita o explícitamente.
En primer lugar, vamos a crear una clase MiClase
que conforme a ProtocoloGenerico
. Debemos definir miTipo
. Hay dos maneras de definir como se indicó anteriormente.
Definir Tipo Asociado implicitamente
Puede definir miTipo
basado en el valor asociado con miPropiedad
.
class MiClase: ProtocoloGenerico {
var miPropiedad: miTipo = "Bob"
}
Ahora, miTipo
se ha definido como String basado en “Bob”. Sin embargo, puedes dejar que Swift lo infiera como se muestra a continuación:
class MiClase: ProtocoloGenerico {
var miPropiedad = "Bob" // miTipo is "String"
}
Definir Tipo Asociado explicitamente
También puedes definir el tipo asociado miTipo
llamando a typealias
. ¿Qué? Vamos a ver.
class MiClase: ProtocoloGenerico {
typealias miTipo = String
var miPropiedad: miTipo = "Bob"
}
Si desea definir el tipo asociado miTipo
, puede utilizar typealias
. Por supuesto, no es necesario ya que puede definir miTipo
implícitamente como hemos visto.
Hasta ahora, has definido miTipo
como String
. Vamos a crear una estructura que se ajuste a ProtocoloGenerico
pero miTipo
es Int
en su lugar.
struct MiEstructura: ProtocoloGenerico {
var miPropiedad = 1996
}
Has declarado implícitamente que miTipo
es Int
basado en el valor de 1996
.
Si escuchas Protocol Associated Types (PATs), sólo significa protocolos genéricos.
Extensiones de Protocolos y Restricciones de Tipos
Como ya sabes, la extensión de protocolo es increíble porque proporciona implementaciones predeterminadas sin tener que definir los métodos y propiedades necesarios. Veamos:
extension ProtocoloGenerico {
static func saludar() {
print("Hola, soy Bob")
}
}
Cualquier cosa que adopte ProtocoloGenerico
ahora contiene esta magia:
MiClase.saludar() // Hola, soy Bob
MiEstructura.saludar() // Hola, soy Bob
Pero si solo quieres que miTipo
como String
tenga el método saludar()
. ¿Cómo lo harias?
Usando la instrucción Where
No te preocupe si nunca has usado where
. Es sólo una forma más corta de escribir una declaración if-else
.
Vamos a agregar saludar()
para aquellos que no sólo se ajustan a ProtocoloGenerico
pero también tiene el tipo asociado como String.
extension ProtocoloGenerico where miTipo == String {
func saludar(){
print("Hola, soy Bob")
}
}
La cláusula where
indica que si miTipo
es String
, procede, si no ignora el bloque de extensión completo.
Hagamos la prueba
Si recuerdas, MiClase
tiene String
y MiEstructura
tiene Int
.
let instanciaMiClase = MiClase().saludar() // "Hola, soy Bob"
Probemos hacerlo con MiEstructura
:
let instaciaMiEsctructura = MiEstructura() // Error
Múltiples condiciones Where con Self
Puedes agregar varias cláusulas where
para que la extensión sea más específica. Todo lo que tienes que hacer es agregar más condiciones al final.
Esta vez, vamos a añadir una restricción más que solo MiClase
puede tener el método saludar()
.
extension ProtocoloGenerico where type == String, Self == MiClase {
func saludar(){
print("Hola, soy Bob")
}
}
El Self
se refiere a la estructura/clase/enum que conforma ProtocoloGenerico. Como resultado, sólo MiClase
tendrá el método saludar()
.
Sobreescribir el Tipo Asociado
Hasta ahora, en ProtocoloGenerico
, no hemos definido associatedtype dentro del propio protocolo.
protocol ProtocoloGenerico {
associatedtype miTipo
var miPropiedad: miTipo { get set }
}
El tipo de miTipo
ha sido definido por aquellos que se ajustan al protocolo. Sin embargo, también puede predefinirse tipos asociados dentro de un protocolo.
Tipos Asociados predefinidos en un Protocolo
Vamos a crear un protocolo llamado Familiar
que contiene un tipo asociado llamado TipoFamilia
. Pero, se pre-define su tipo como Int
.
protocol Familiar {
associatedtype TipoFamilia = Int
func obtenerNombre() -> [TipoFamilia]
}
Es muy parecido a typealias
.
Adoptar Tipos Asociados predefinidos en el Protocolo
class FamiliaNumerica: Familiar {
func obtenerNombre() -> [TipoFamilia] {
return [1, 2, 3]
}
}
tambien podría ser:
class FamiliaNumerica: Familiar {
func obtenerNombre() -> [Int] {
return [1, 2, 3]
}
}
Ahora creamos una instancia:
let familiaDeNumeros = FamiliaNumerica() // FamiliaNumerica<Int>
Sin embargo, es posible anular/cambiar el tipo predefinido de un protocolo.
Sobreescribir el Tipo Asociado
Primero, crearemos una estructura genérica llamada FamiliaNormal
que se ajustará a Familiar
, eso significa que Familiar
forzará a la estructura a trabajar con Int
, pero esto no es lo que queremos.
Deseamos que la estructura funcione con String, ya que una familia normal debe tener nombres como “Bob” o “Bobby” en lugar de 1, 2, 3.
struct FamiliaNormal<T: ExpressibleByStringLiteral>: Familiar {
func obtenerNombre() -> [T] {
return ["Bob", "Bobby", "Lee"]
}
}
Ahora creamos una instancia:
let familiaNormal = FamiliaNormal() // FamiliaNormal<String>
¿Cómo es esto posible? Bueno, si presionas option + clic sobre String en Swift, descubrirás que String se ajusta a ExpressibleByStringLiteral.
// Libreria de Swift
extension String : ExpressibleByStringLiteral {}
Descarga el Código fuente
Últimos Comentarios
En este tutorial, has aprendido cómo sobreescribir Tipos Asociados e incluso combinar protocolos con genéricos. ¿Cómo fue tu aprendizaje? Hemos hablado de muchas teorías en este artículo. Por supuesto, si usted desea aprender la programación práctica orientada protocolo, RxSwift, MVVM, te recomiendo encarecidamente a unirte a mi lista de correo.