Puede parecer algo de lo que se puede prescindir, pero hay detalles que no deben ser ignorados a la hora de usar closures para usar de manera segura la memoria.
CLOSURE
Primero, ¿qué es un closure?, son bloques que contienen una funcionalidad dentro de ellos, a la cual puedes acceder cuando lo necesites e incluso hacer uso de ella en tu código, si no es por decir que segura y recomendablemente, lo harás. Los closures capturan y contienen referencias a cualquier variable o constante dentro del contexto en el que están definidas.
Y ojo a la palabra “referencias”, pues como vimos en la manera en la que funciona ARC para alojar y desalojar memoria, al crear referencias tenemos la posibilidad de crear tres tipos de referencias, las fuerte, débiles e impropias. por ello, es un detalle importante el saber que tenemos que tener cuidado a la hora de utilizar un closure relacionado a una instancia de una clase.
Porque a saber, cuando tu utilizas un closure para definir cierta propiedad (self.someProperty) con cierto método (self.someMethod) haces referencia a una instancia del mismo, por lo tanto puedes crear referencias fuertes, lo que provocará un ciclo de referencias fuertes que jamás se romperá, por lo tanto tu código después no podrá utilizar la funcionalidad ARC para administrar correctamente la memoria. Por ejemplo, veámoslo con el siguiente código:
classHTMLElement{
let name: Stringlet text: String?
lazyvar asHTML: () -> String = {
iflet text = self.text {
return"<\(self.name)>\(text)</\(self.name)>"
} else {
return"<\(self.name) />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
Como podemos ver, este closure nos devolverá en el caso de nosotros asignarle los parámetros, un texto en HTML con su respectiva etiqueta, por ejemplo <p> para párrafo o <h1> para título, si nosotros hacemos
let heading = HTMLElement(name: "h1")
let defaultText = "some default text"
heading.asHTML = {
return"<\(heading.name)>\(heading.text ?? defaultText)</\(heading.name)>"
}
print(heading.asHTML())
Nosotros estamos definiendo que la constante heading sea del tipo HTMLElement, haciendo uso del closure escribimos el cuerpo de lo que queremos escribir y en qué formato, además le definimos cierto nombre para no hacer un force unwrapping, el cuál es un texto default, esto imprimiría “<h1>some default text</h1>”
Pero se ha hecho ahora una referencia fuerte, para aterrizarlo mejor, podemos escribir:
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
E imprimiría tranquilamente
<p>hello, world</p>
Pero, existe el problema de la referencia fuerte porque estás llamando al closure provocando un enlace fuerte entre él y la instancia, ahora si hacemos
paragraph = nil
No obtendríamos el deinit y no imprimiría nada en lo absoluto.
Para resolver este ciclo, Swift propone una solución elegante.
¿Cómo resolver el Strong Reference Cycle?*
Para solucionarlo necesitamos definir una capture List, una lista de captura define un conjunto de reglas para usarse cuando se llamen a uno o más tipos de referencias dentro del cuerpo del Closure.
¿Cómo se definen las listas de captura?
Se definen de la siguiente manera y dentro del código del closure:
lazy varsomeClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> Stringin//Aquí iría el cuerpo del closure
}
La lista de captura va antes de los parámetros del Clousure y del valor que devuelven en caso de hacerlo.
Si no recibe parámetros debido a que estos se proveen por el contexto se definiría así
lazyvar someClosure: () -> String = {
[unownedself, weak delegate = self.delegate!] in//Cuerpo de closure
}
Está la lista pero no hay parámetros ni se expresa el tipo de dato que devuelve.
EN EL CÓDIGO ANTERIOR
Ahora tenemos lo siguiente en nuestro código anterior
classHTMLElement{
let name: Stringlet text: String?
lazyvar asHTML() -> String = {
[unownedself] iniflet text = text {
return"<\(self.name)>\(text)</\(self.name)>"
} else {
return"<\(self.name)> />"
}
}
init(name: String, text: String? = nil) {
self.name = name
self.text = text
}
deinit {
print("\(name) is being deinitialized")
}
}
Y ahora hacemos
var paragraph: HTMLElement? = HTMLElement(name: "p", text: "hello, world")
print(paragraph!.asHTML())
Ahora existe una referencia débil entre la instancia de la clase y el closure que estamos usando, si ahora hacemos
paragraph = nil
Imprimirá
p isbeing deinitialized
Hay muchas más cosas sobre las listas de captura, si te sirvió este post y quieres que haga un tema sobre ello, házmelo saber, con gusto hago otro, si crees que le faltó, hazme saber en qué puedo mejorar.
Me apoyé de aquí:
ARC
Y hago referencias fuertes xD a mi otro post:
ARC mío
GRACIAS POR SU ATENCIÓN
Muchas gracias por el aporte, son conceptos que con la práctica se quedaran muy grabados.