Vamos a hechar un ojo sobre los protocolos en Swift. Además de ver cómo crear métodos opcionales, añadir comportamiento a los protocolos y comportamiento por defecto también.

Últimamente estoy reescribiendo Kino desde 0 en Swift, esto me ha llevado a crear un par de frameworks open source (en desarrollo todavia) y aprender algunas cosas sobre los protocolos.

Como ya habreis leido, con Swift 2.0 surgió un nuevo paradigma llamado Protocol Oriented Programming el cual nos ayuda a reutilizar muchísimo código y usar composición antes que herencia.

El principal problema que he tenido con esto ha sido que en todos los blogs que he leído sobre ello, sólo han hablado de cómo dar funcionalidad a los protocolos, dotándolos de funciones que reutilizabamos dentro de la clase que los implementa o con cosas muy genéricas como dropIf y este estilo de cosas. Entonces, llegado el momento de empezar a crear mis propios protocolos empezé a encontrarme con problemas. Métodos que no se sobrescribían, dificultades para añadir cosillas por defecto y demases.

Finalmente y con bastante pruebas en playgrounds, he conseguido sacar lo siguiente.

Pasemos a los casos prácticos:

Añadiendo funcionalidad a un protocolo.

Imaginemos el objeto Persona, una persona puede comer, pero en este mundo tambien podrían comer los animales, ¿no?. En este caso, una solución facil es hacer un protocolo Eater que nos aporte la funcionalidad de comer, veámoslo:

protocol Eater {

}

extension Eater {
	func eat() {
		print("I'm eating")
	}
}

Con este protocolo hemos conseguido que cualquier objeto que lo implemente tenga la funcionalidad comer.

Desde nuestra clase persona podemos llamar a comer y llamará a esta función que hemos creado.

Ahora bien, nuestra clase persona no queremos que imprima por consola que esta comiendo, queremos que llame a la función engordar.

Pues algo muy sencillo será hacer lo siguiente.

class Person: Eater {

	func fatten() {
		print("I'm getting fat")
	}

	func eat() {
		self.fatten()
	}
}

¡Ya tenemos a una persona que engorda cuando come! 😄 comprobemoslo:

let person = Person()
person.eat //Will print: I'm getting fat

Con esto hemos conseguido añadir funcionalidad a un objeto y sobrescribirla para nuestro tipo persona.

PERO, ¿Qué pasa cuando tratamos a nuestra clase persona como un tipo Eater?, nuestra clase persona imprimirá “I’m eating”

let eater: Eater = Person()
eater.eat() //Will print "I'm eating"

La realidad es que esto no es lo que esperamos. Nosotros sabemos que en realidad ese Eater es un objeto del tipo persona. Esperamos que engorde, no que hable 😑

¿Que esta pasando? En swift, cuando extendemos un protocolo para añadirle funcionalidad, en realidad estamos haciendo eso, añadirle funcionalidad. No estamos añadiendo un comportamiento por defecto. Al tratar Person como Eater, en realidad ejecutamos el metodo dentro de Eater.

Añadir comportamiento por defecto y sobrescribirlo.

¿Cómo podemos conseguir que nuestra persona engorde incluso cuando se la trata como un Eater? Pues la solucion es muy sencilla, vamos a hacer que todo objeto que implemente el protocolo Eater tenga que implementar la fucion eat.

protocol Eater {
	func eat()
}

Bien, ahora cuando llamamos a nuestra clase persona y la tratamos como Eater, si ejecuta el eat dentro de persona, pero espera!, 🤔¿Ahora tengo que implementar la función eat en todos y cada uno de los tipos que quiero que sean Eater?🤔 -NO

Para añadir un comportamiento por defecto a nuestro protocolo Eater sólo tendremos que hacer lo mismo que antes, pero mantenemos la firma func eat() dentro de Eater

protocol Eater {
	func eat()
}

extension Eater {
	func eat() {
		print("I'm eating")
	}
}

🎉Por fín hemos conseguido un comportamiento por defecto y la capacidad de sobrescribirlo.🎉

Veamos qué hace persona ahora.


let eater: Eater = Person()
eater.eat() //Will print "I'm getting fat"

¿Metodos opcionales?

Sí, usando los protocolos de swift, sin necesidad de hacer grandes chanchus, podemos tener métodos opcionales, ¿el truco?, igual que antes, vamos a añadir un comportamiento por defecto, sólo que esta vez no hará nada.


protocol WithOptionals {

	func normalMethod()

	//Mark - Optionals
	func optionalMethod()

}

extension WithOptionals {

	func optionalMethod() { }

}

De esta forma tan sencilla, hemos conseguido tener un método que nuestros objetos no se verán obligados a usar, y no sólo eso, no tendremos que estar comprobando si nuestro objeto responde o no a X selector como en el antiguo objc.

Aplicaciones útiles.

Una de las cosillas mas útiles que le he encontrado a esto es tener variables con un valor por defecto. Y sí, sin llegar a escribir una linea fuera de nuestro protocolo (y su extensión).

Veamos un caso.


protocol Greeter {

	var greeting: String

	func greet()

	func getGreeting() -> String
}

extension Greeter {

	var greeting {
		get {
			return self.getGreeting()
		}
	}

	func greet() {
		print(self.greeting)
	}

	func getGreeting() -> String {
		return "Hey man!"
	}

}

¿Con esto qué hemos conseguido?

Todos nuestros objetos que implementen el protocolo Greeter, no solo tendran por defecto el método saludar, que por supuesto podemos sobrescribir cómo nosotros queramos, también tenemos de gratis una variable greeting que podremos tener o no en nuestra clase y usarla como queramos. Un ejemplo:

class Person: Greeter {

}

let person = Person()
person.greet() //Will print "Hey man!"

Ahora nuestra clase persona puede saludar, pero podemos conseguir que nuestra clase persona tenga un mensaje propio.


class Person: Greeter {
	let greeting = "Hi, How are you?"
}

let person = Person()
person.greet() //Will print "Hi, How are you?"

O simplemente que salude de forma distinta:

class CrazyPerson: Person, Eater, Greeter {

	func greet() {
		self.eat()
	}

}

let person = Person()
person.greet() //Will print "I'm getting fat"

Como podemos ver, con un par de truquis podemos conseguir que los protocolos nos faciliten mucho la vida, reutlizar un montón de código con poco esfuerzo y bien organizado.