Las entrevistas de iOS testean el sistema de tipos de Swift, cómo ARC maneja la memoria, y si puedes razonar sobre el estado de la UI — más al menos una pregunta donde un retain cycle se esconde a plena vista. Aquí están las preguntas hechas más a menudo, cada una con una respuesta modelo.
Cada pregunta viene con una respuesta modelo con la que puedes comparar la tuya.
1
¿Qué problema resuelven los optionals, y cómo desenvuelves uno de forma segura?
Respuesta
Un optional hace que "este valor puede estar ausente" sea parte del tipo, así el compilador te obliga a manejar el caso vacío en vez de crashear en runtime como haría un null pointer. Desenvolver seguro significa if let o guard let para un valor que necesitas en scope, nil-coalescing (??) para un default, y optional chaining cuando solo te importa el camino feliz. Force unwrapping con ! es una promesa al compilador de que el valor existe — aceptable para outlets y errores de programador, nunca para datos de red.
2
¿Cuál es la diferencia entre un struct y una class en Swift? ¿A cuál recurres por defecto?
Respuesta
Los structs son tipos de valor: asignar uno lo copia, no pueden heredar, y se asignan en el stack cuando es posible. Las classes son tipos de referencia con identidad, herencia, deinit, y overhead de ARC. La convención de Swift es empezar con un struct y recurrir a una class solo cuando necesitas semántica de referencia — estado mutable compartido, un requisito de interoperabilidad con Objective-C, o un ciclo de vida que debes observar.
3
¿Cómo funciona ARC, y qué causa un retain cycle?
Respuesta
ARC rastrea cuántas referencias fuertes apuntan a un objeto y lo desasigna cuando el conteo llega a cero — son llamadas retain/release insertadas en tiempo de compilación, no un garbage collector de runtime. Un retain cycle pasa cuando dos objetos tienen referencias fuertes entre sí, así que ningún conteo llega a cero: un caso clásico es un view controller que posee un closure que captura self fuertemente. Lo rompes con [weak self] o [unowned self] en la lista de captura.
4
¿Cuándo usas weak versus unowned?
Respuesta
Ambos evitan incrementar el retain count. weak hace la referencia opcional y la pone en nil cuando el objeto se desasigna, así que es seguro si el otro objeto puede morir primero — los delegates son el ejemplo estándar. unowned no es opcional y asume que el objeto referenciado sobrevive más que tú; si esa suposición se rompe, la app crashea. Usa unowned solo cuando la relación de vida está genuinamente garantizada, y weak cuando no estás seguro.
5
¿Cuál es la diferencia entre @State, @Binding, @StateObject y @ObservedObject?
Respuesta
@State es estado local de tipo valor que la vista posee; SwiftUI lo guarda fuera del struct para que sobreviva re-renders. @Binding es una referencia de lectura-escritura a estado que posee alguien más, que es cómo un hijo muta el valor de un padre. @StateObject crea y posee un objeto observable de tipo referencia y se inicializa exactamente una vez. @ObservedObject observa un objeto poseído en otro lugar — usarlo donde necesitabas @StateObject recrea el objeto en cada re-render y resetea su estado silenciosamente.
6
¿Cómo decide SwiftUI volver a renderizar una vista?
Respuesta
Una vista es un struct liviano que describe la UI; SwiftUI reconstruye su body cada vez que una dependencia que leyó cambia, luego hace diff del resultado contra el árbol anterior y aplica solo las diferencias al render subyacente. Las dependencias son los property wrappers que una vista realmente lee, más el Environment. Los problemas de performance casi siempre vienen de estado colocado demasiado alto en el árbol, así un cambio no relacionado invalida un subárbol grande.
7
¿Qué cambia async/await comparado con completion handlers y GCD?
Respuesta
async/await deja que el código asíncrono se lea de arriba a abajo, así el manejo de errores usa try/catch ordinario y no hay "pirámide de la perdición" ni camino de completion olvidado. El compilador exige que los awaits pasen en un contexto async, y la concurrencia estructurada ata los ciclos de vida de tareas hijas a su padre, así la cancelación se propaga. GCD sigue siendo relevante para código legacy y para control fino de colas, pero el código nuevo debería usar async/await por defecto, con Combine reservado para streams genuinos de valores a través del tiempo.
8
¿Qué es un escaping closure y por qué existe la palabra clave?
Respuesta
Un closure es escaping cuando se guarda y se llama después de que la función retorna — un completion handler de red, por ejemplo. Swift requiere la anotación @escaping porque un closure no-escaping se puede optimizar más agresivamente y no puede capturar self fuertemente por accidente. La anotación también es la señal para pensar en la semántica de captura: los closures escaping son donde nacen los retain cycles.
9
Explica el ciclo de vida de UIViewController. ¿Dónde pones el código de layout?
Respuesta
viewDidLoad corre una vez después de que la jerarquía de vistas carga y es donde haces setup de una sola vez. viewWillAppear y viewDidAppear corren en cada presentación y son para trabajo que debe repetirse, como refrescar datos o iniciar una animación. El layout que depende de tamaños de frame finales pertenece a viewDidLayoutSubviews, porque en viewDidLoad los bounds todavía no son finales — poner matemática de frames ahí es un bug muy común que solo se ve en un tamaño de pantalla distinto.
10
¿Cómo decides entre Core Data, SwiftData, y simplemente escribir archivos?
Respuesta
Core Data vale su complejidad cuando tienes datos relacionales, necesitas consultas, faulting, y seguimiento de cambios a través de un grafo de objetos grande. SwiftData es el wrapper moderno nativo de Swift sobre el mismo stack y es el default para apps nuevas en versiones recientes de OS. Para un puñado de configuraciones, usa UserDefaults; para una caché de modelos decodificados, archivos Codable simples o un key-value store liviano son mucho más simples de razonar que un contenedor persistente completo.
11
¿Cómo diagnosticas un lanzamiento lento de la app o una fuga de memoria?
Respuesta
Para tiempo de lanzamiento, usa la plantilla App Launch de Instruments para ver qué corre antes del primer frame; los culpables usuales son trabajo síncrono de disco o red en didFinishLaunching y grafos de dependencias pesados construidos eagerly. Para fugas, el Memory Graph Debugger muestra objetos que deberían haberse desasignado y quién todavía los apunta, lo que revela retain cycles directamente. El instrumento Leaks atrapa fugas clásicas, pero el grafo es mejor para ciclos porque nombra la referencia que retiene.
12
¿Qué testeas en iOS, y qué deliberadamente no testeas?
Respuesta
Los unit tests cubren lógica de negocio, view models, y transformaciones puras — cualquier cosa con inputs y outputs y sin dependencia de UIKit. XCUITest cubre un pequeño número de flujos críticos end-to-end como sign-in y compra, porque los tests de UI son lentos y frágiles en volumen. Lo que usualmente no vale la pena testear es el comportamiento del framework de Apple y el layout a nivel de pixel; los snapshot tests pueden ayudar ahí pero se rompen en cada bump de versión de OS, así que mantenlos pocos y deliberados.