¿Conoces los Tipos de Capture Lists y de completion Handler en Swift? Una Pregunta Raramente nueva en Entrevistas iOS.
Recientemente, estuve buscando un nuevo trabajo y tuve varias entrevistas interesantes aquí en México. Una de las preguntas que más me llamó la atención fue sobre los tipos de capture lists.
Personalmente, creo que, con la llegada de async/await, estas preguntas podrían no ser tan relevantes, a menos que estés trabajando con arquitecturas como TCA o con Combine, donde puedes encontrarte con problemas como la Pyramid of Doom.
Este año, he notado que las preguntas en entrevistas para trabajos móviles se están volviendo cada vez más peculiares. En lugar de enfocarnos solo en los tipos de capture lists, creo que sería más útil preguntar cómo manejar un capture list, ya que tanto un capture list como un completion handler funcionan de manera similar a una función. Pueden recibir y devolver valores, lo que los hace bastante flexibles.
Así que, ¡vamos a explorar los tipos de capture lists y completion handlers !
Pero primero:
¿Que es un Capture List?
Un capture list es una característica de Swift que permite controlar cómo un closure captura las variables y constantes que se encuentran en su contexto. Cuando una closure accede a propiedades de su entorno, puede crear referencias fuertes que, si no se gestionan correctamente, pueden llevar a ciclos de retención de memoria. Con los capture lists, especificamos cómo queremos capturar esas referencias, como por ejemplo, de forma débil (weak) o sin retención (unowned), para evitar problemas de memoria.
class Example {
var value = 10
lazy var closure: () -> Void = { [weak self] in
guard let self = self else { return }
print(self.value) // Captura `self` débilmente para evitar ciclos de referencia
}
}
// Uso
let example = Example()
example.closure() // Imprime 10En este ejemplo:
[weak self]es el capture list que captura aselfde forma débil, para evitar un retain cycle (ciclo de retención de memoria).- La palabra
lazypermite que el closure se cree solo cuando se llame. - Capturar
selfdébilmente evita que el closure retenga una referencia fuerte a la instancia deExample. Así, siExamplees liberado de memoria, no quedará retenido dentro de la closure.
Otro ejemplo puede ser:
class Example {
var value = 10
lazy var closure: () -> Void = { [unowned self] in
print(self.value)
}
}En este caso, el capture list [unowned self] captura self de forma que asume que siempre existirá en el momento en que se use en el closure. A diferencia de weak, unowned no permite que self sea nil, por lo que esta técnica es útil si sabes que Example seguirá existiendo mientras la closure esté activa.
Otro ejemplo puede ser :
func makeIncrementer(incrementAmount: Int) -> () -> Int {
var total = 0
return { [incrementAmount] in
total += incrementAmount
return total
}
}Este ejemplo parece complejo, pero en realidad no genera un ciclo de retención. Aquí, incrementAmount es un tipo Int, que en Swift es un tipo por valor. Esto significa que se copia en el closure, y como no es una referencia, no crea ciclos de retención. total también es una variable local dentro de la función, por lo que solo existe cuando se llama a la función.
A diferencia de este ejemplo:
class Example {
var value = 10
func doSomething() {
let closure: () -> Void = { [self] in
print(self.value)
}
}
}En este ejemplo, [self] en el capture list captura self con una referencia fuerte, lo cual crea un ciclo de retención. El closure mantiene self en memoria y, a su vez, self mantiene una referencia a el closure, lo que evita que ambos se liberen de la memoria, creando un retain cycle.
Get Jael Ruvalcaba’s stories in your inbox
Join Medium for free to get updates from this writer.
¡Y con esto, hemos explorado todos los tipos de capture list! La verdad es que fue una pregunta compleja 😂🤣, pero al final, lo importante es entender cuándo usar weak y unowned para manejar las referencias de forma segura y eficiente.
Pero algo interesante que también me han preguntado es, si existen diferentes tipos de completion handlers. Así que vamos a revisar qué son y los tipos que existen. ¡Vamos a ello!
¿Que es un Completion Handler ?
Un completion handler es un tipo de closure que se pasa como argumento a una función y se ejecuta después de que la función ha terminado su trabajo, normalmente en un contexto asíncrono. Un completion handler puede recibir parámetros, lo que permite devolver valores o resultados a la función que lo llamó.
func fetchData(completion: (String) -> Void) {
let data = "Datos obtenidos"
completion(data)
}
fetchData { result in
print(result)
}Aquí, fetchData recibe un completion handler que acepta un String y no devuelve ningún valor (Void). Este tipo de completion handler es muy común cuando queremos que el código que llama a la función reciba un valor (en este caso, el String "Datos obtenidos") y luego realice alguna acción con él, como imprimirlo.
Otro completion handler muy comun es:
enum NetworkError: Error {
case badURL
case requestFailed
}
func fetchData(completion: (String?, NetworkError?) -> Void) {
let success = true
if success {
completion("Datos obtenidos", nil)
} else {
completion(nil, .requestFailed)
}
}
fetchData { data, error in
if let error = error {
print("Error: \(error)")
} else if let data = data {
print(data)
}
}En este caso, el completion handler puede devolver un String (si la operación es exitosa) o un error (NetworkError), dependiendo del resultado de la operación. Este tipo de completion handler se utiliza en UIKit y es especialmente útil para gestionar tareas asíncronas que pueden fallar, como solicitudes de red.
El uso de genéricos en completion handlers es otra opción, aunque el concepto central de los completion handlers sigue siendo el mismo. Sin embargo, lo más importante al hablar de completion handlers es entender @escaping, un tema que a veces se pasa por alto en entrevistas, aunque es clave en el manejo de operaciones asíncronas.
¿Qué es @escaping?
@escaping se usa para indicar que el closure puede ejecutarse después de que la función en la que fue definido haya terminado. Esto es fundamental en tareas asíncronas, como las solicitudes de red, porque permite que el closure sobreviva a la función que lo invocó.
En términos simples, si marcamos un closure como @escaping, significa que queremos usar sus valores o realizar acciones con ellos incluso después de que la función haya finalizado.
Ejemplo con @escaping
func fetchData(completion: @escaping (String) -> Void) {
DispatchQueue.global().asyncAfter(deadline: .now() + 1) {
let data = "Datos obtenidos"
completion(data)
}
fetchData { result in
print(result)
}En este ejemplo, @escaping permite que el completion handler se ejecute incluso cuando fetchData ya ha terminado, lo cual es crucial en código asíncrono. Sin @escaping, el closure se tendría que ejecutar dentro de la misma función, limitando su alcance en tareas que requieren más tiempo.
En resumen:
@escapingpermite que el closure se ejecute más tarde, fuera de la función.- Es esencial para operaciones asíncronas donde se necesita mantener acceso a los valores después de que la función inicial termine.
En conclusión, es importante estar preparado para preguntas inesperadas en entrevistas, especialmente sobre temas como “tipos de completion handlers” o “tipos de capture lists”. Como vimos, en realidad no hay “tipos” de estos elementos, sino que su funcionalidad depende del contexto en el que se usan.
Recuerda no dejarte intimidar por el entrevistador; a veces, pueden estar probando tus conocimientos en áreas menos relevantes o incluso haciendo preguntas fuera de contexto. También es válido solicitar que las entrevistas técnicas sean grabadas, ya que esto ayuda a mantener un proceso justo y transparente. A veces, un entrevistador puede dar un mal feedback debido a la inseguridad de ver a alguien que domina el tema; eso no debería afectar tu evaluación.
Para los entrevistadores, la honestidad y el respeto son esenciales: no uses preguntas de relleno ni te sientas amenazado si alguien demuestra un conocimiento fuerte. Esto no es una competencia, sino una oportunidad de fortalecer el equipo con nuevos talentos.










