Read in other languages: English 🇺🇸, Polska 🇵🇱, German 🇩🇪, French 🇫🇷, Spanish 🇪🇸, Українська 🇺🇦.
1. ¿Para qué se utiliza principalmente Swift en el desarrollo moderno?
Swift se utiliza principalmente para crear aplicaciones para las plataformas de Apple:
- iOS
- macOS
- watchOS
- tvOS
- visionOS
Se utiliza sobre todo para:
- Desarrollo de aplicaciones móviles
- Desarrollo nativo para plataformas Apple
- Desarrollo backend con frameworks como Vapor
- Herramientas de línea de comandos y scripts
Swift es valorado por su seguridad, rendimiento y sintaxis moderna.
2. ¿Cuál es la diferencia entre `let` y `var`?
let crea una constante y var crea una variable.
- Usa
letcuando el valor no debe cambiar. - Usa
varcuando el valor puede cambiar más adelante.
let name = "Alice"
var age = 25
age = 26 // Permitido
// name = "Bob" // ErrorUsar let por defecto hace que el código sea más seguro y más fácil de entender.
3. Explica la inferencia de tipos en Swift.
La inferencia de tipos significa que Swift puede determinar automáticamente el tipo de un valor a partir de los datos asignados, por lo que no siempre es necesario escribir el tipo de forma explícita.
let title = "Swift" // String
let year = 2025 // Int
let isActive = true // BoolTambién puedes especificar el tipo manualmente cuando quieras expresar una intención más clara:
let score: Double = 99La inferencia de tipos hace que el código sea más corto sin perder seguridad de tipos.
4. ¿Qué son los optionals y por qué son importantes?
Un optional es un tipo que puede contener:
- un valor
nil
Los optionals son importantes porque hacen explícita la ausencia de un valor y ayudan a evitar crashes en tiempo de ejecución causados por datos faltantes.
var nickname: String? = "Sam"
nickname = nilSin los optionals, Swift no podría representar de forma segura valores que pueden no existir, como por ejemplo:
- Entrada del usuario
- Campos de respuestas de red
- Valores de base de datos
- URLs cuya inicialización puede fallar
5. ¿Cuál es la diferencia entre optional binding y force unwrapping?
El optional binding extrae de forma segura el valor de un optional.
El force unwrapping extrae el valor directamente y provoca un crash si el optional es nil.
let name: String? = "Alice"
if let unwrappedName = name {
print(unwrappedName)
}let name: String? = "Alice"
print(name!)Usa optional binding en la mayoría de los casos.
Usa force unwrapping solo cuando estés completamente seguro de que el valor no es nil.
6. ¿Qué es optional chaining y cuándo se usa?
Optional chaining es una forma de acceder de manera segura a propiedades, métodos o subscripts sobre un valor optional.
Si el optional es nil, toda la expresión devuelve nil en lugar de provocar un crash.
class User {
var address: Address?
}
class Address {
var city: String = "London"
}
let user = User()
let city = user.address?.cityUsa optional chaining cuando trabajes con valores optional anidados, especialmente en:
- Objetos de modelo
- Respuestas de API
- Referencias de UI que pueden no existir
7. ¿Qué es el operador nil coalescing?
El operador nil coalescing es ??.
Proporciona un valor por defecto cuando un optional es nil.
let username: String? = nil
let displayName = username ?? "Guest"En este ejemplo, displayName pasa a ser "Guest".
Usa ?? cuando quieras un valor de respaldo para un optional.
8. ¿Qué son las tuplas y cuándo deberías usarlas?
Una tupla es un grupo de valores combinados en un único valor compuesto.
let person = ("Alice", 28)También puedes nombrar los valores de la tupla:
let httpResponse = (statusCode: 200, message: "OK")
print(httpResponse.statusCode)Usa tuplas para valores agrupados de corta duración, por ejemplo:
- Devolver varios valores desde una función
- Representar un pequeño conjunto de datos relacionados
- Datos locales temporales
Para datos más complejos o reutilizables, es mejor usar struct o class.
9. ¿Cuáles son los principales tipos de colecciones en Swift?
Los principales tipos de colecciones en Swift son:
ArraySetDictionary
Almacena una lista ordenada de valores.
let numbers = [1, 2, 3]Almacena valores únicos y no ordenados.
let uniqueNumbers: Set = [1, 2, 3]Almacena pares clave-valor.
let userAges = ["Alice": 25, "Bob": 30]Estas colecciones se usan constantemente en el desarrollo con Swift.
10. ¿Cuál es la diferencia entre Array, Set y Dictionary?
La diferencia está en cómo almacenan y cómo permiten acceder a los datos.
| Type | Stores | Order | Uniqueness | Access |
|---|---|---|---|---|
Array |
A list of values | Ordered | Duplicates allowed | By index |
Set |
Unique values | Unordered | Duplicates not allowed | By value lookup |
Dictionary |
Key-value pairs | Unordered | Keys must be unique | By key |
let array = ["a", "b", "a"]
let set: Set = ["a", "b", "a"]
let dictionary = ["name": "Alice", "city": "Paris"]Arraymantiene el orden y puede contener duplicados.Setelimina duplicados.Dictionaryasocia claves con valores.
11. ¿Qué son los value types frente a los reference types?
Los value types se copian cuando se asignan o se pasan de un lugar a otro. Los reference types comparten la misma instancia.
structenumtuple
struct User {
var name: String
}
var user1 = User(name: "Alice")
var user2 = user1
user2.name = "Bob"
print(user1.name) // Alice
print(user2.name) // Bobclass
class Person {
var name: String
init(name: String) {
self.name = name
}
}
let person1 = Person(name: "Alice")
let person2 = person1
person2.name = "Bob"
print(person1.name) // Bob
print(person2.name) // BobUsa value types por defecto, salvo que necesites estado mutable compartido.
12. ¿Cuál es la diferencia entre `struct` y `class`?
La diferencia principal es que struct es un value type y class es un reference type.
| Feature | struct |
class |
|---|---|---|
| Type | Value type | Reference type |
| Copy behavior | Copied on assignment | Shared by reference |
| Inheritance | Not supported | Supported |
| Deinitializer | Not supported | Supported |
| Identity check | No | Yes with === |
Usa struct en la mayoría de los casos.
Usa class cuando necesites:
- Estado mutable compartido
- Herencia
- Semántica de identidad
13. ¿Qué es copy-on-write?
Copy-on-write es una optimización en la que un valor no se copia de inmediato. Swift solo crea una copia real cuando uno de los valores se modifica.
Esto se usa habitualmente en colecciones estándar como:
ArrayDictionarySet
var numbers1 = [1, 2, 3]
var numbers2 = numbers1
numbers2.append(4)
print(numbers1) // [1, 2, 3]
print(numbers2) // [1, 2, 3, 4]Esto ofrece la seguridad de los value types con un buen rendimiento.
14. ¿Qué es la inmutabilidad y por qué es importante?
La inmutabilidad significa que un valor no puede cambiar después de crearse.
En Swift, los valores declarados con let son inmutables.
let name = "Alice"
// name = "Bob" // ErrorLa inmutabilidad es importante porque:
- Hace el código más seguro
- Reduce errores
- Facilita seguir el estado
- Ayuda a razonar sobre la concurrencia
Usa valores inmutables por defecto y hazlos mutables solo cuando sea necesario.
15. ¿Qué son las computed properties?
Una computed property no almacena un valor directamente. En su lugar, calcula el valor cada vez que se accede a ella.
struct Rectangle {
var width: Double
var height: Double
var area: Double {
width * height
}
}Las computed properties también pueden tener get y set:
struct Square {
var side: Double
var area: Double {
get { side * side }
set { side = sqrt(newValue) }
}
}Usa computed properties cuando un valor dependa de otros valores.
16. ¿Qué son los property observers (`willSet` / `didSet`)?
Los property observers te permiten reaccionar cuando cambia el valor de una stored property.
willSetse ejecuta antes de almacenar el nuevo valordidSetse ejecuta después de almacenar el nuevo valor
class Profile {
var name: String = "" {
willSet {
print("New value: \(newValue)")
}
didSet {
print("Old value: \(oldValue)")
}
}
}Úsalos cuando necesites:
- Registrar cambios
- Actualizar la UI
- Disparar efectos secundarios después de que cambie un valor
17. ¿Qué son las lazy properties?
Una lazy property se inicializa solo cuando se usa por primera vez.
Se declara con la palabra clave lazy y debe ser una var.
class DataManager {
lazy var formatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
return formatter
}()
}Usa lazy properties cuando:
- La inicialización es costosa
- La propiedad puede no llegar a usarse nunca
- La propiedad depende de
selfdespués de la inicialización
18. ¿Qué son los key paths en Swift?
Los key paths son referencias type-safe a propiedades. Permiten acceder a una propiedad de forma indirecta.
struct User {
let name: String
}
let keyPath = \User.name
let user = User(name: "Alice")
print(user[keyPath: keyPath]) // AliceLos key paths son útiles para:
- Ordenar
- Mapear
- APIs genéricas
- Observar o acceder a propiedades anidadas de forma type-safe
19. ¿Qué es el type casting (`is`, `as`, `as?`, `as!`)?
El type casting se usa para comprobar o convertir el tipo de un valor en tiempo de ejecución.
iscomprueba si un valor es de un tipo concretoashace upcast a un supertipoas?hace downcast seguro y devuelve un optionalas!hace downcast forzado y provoca un crash si falla
class Animal {}
class Dog: Animal {}
let animal: Animal = Dog()
print(animal is Dog) // true
let dog1 = animal as? Dog // Safe
let dog2 = animal as! Dog // Unsafe if wrong typeUsa as? en la mayoría de los casos.
Usa as! solo cuando el fallo sea imposible.
20. ¿Cuál es la diferencia entre `==` y `===`?
== comprueba la igualdad de valor.
=== comprueba la identidad de referencia.
Se usa para comparar si dos valores son iguales.
let a = 5
let b = 5
print(a == b) // trueSe usa solo con clases para comprobar si dos referencias apuntan a la misma instancia.
class User {}
let user1 = User()
let user2 = user1
let user3 = User()
print(user1 === user2) // true
print(user1 === user3) // falsePor tanto:
==significa "mismo valor"===significa "mismo objeto en memoria"
21. ¿Cómo funciona `switch` en Swift (pattern matching)?
switch en Swift es potente y admite pattern matching.
Puede hacer match con:
- Valores exactos
- Múltiples valores
- Rangos
- Tuplas
- Casos de enum
- Valores con condiciones
A diferencia de algunos lenguajes, switch en Swift debe ser exhaustivo.
Eso significa que todos los casos posibles deben manejarse.
let number = 7
switch number {
case 1:
print("One")
case 2...9:
print("Between 2 and 9")
default:
print("Other")
}switch en Swift se usa con frecuencia porque es más seguro y expresivo que
una cadena larga de sentencias if.
22. ¿Qué es `fallthrough` y cuándo debería evitarse?
fallthrough hace que la ejecución continúe desde un case al siguiente
case dentro de un switch.
Por defecto, Swift no hace fall through automáticamente.
let number = 1
switch number {
case 1:
print("One")
fallthrough
case 2:
print("Two")
default:
break
}Salida:
One
TwoEvita fallthrough en la mayoría de los casos porque:
- Hace la lógica menos clara
- Puede causar comportamientos inesperados
- Normalmente tiene alternativas más limpias
Úsalo solo cuando realmente quieras que también se ejecute el siguiente caso.
23. ¿Qué es una sentencia `guard` y cuándo se usa?
guard se usa para salir temprano.
Comprueba una condición y, si la condición falla, la ejecución debe abandonar
el scope actual.
func printName(_ name: String?) {
guard let name = name else {
print("Name is missing")
return
}
print(name)
}Usa guard cuando:
- Falta un valor necesario
- No se cumplen las precondiciones
- Quieres evitar un anidamiento profundo
Hace que el código sea más plano y más fácil de leer.
24. ¿Cuál es la diferencia entre `if let` y `guard let`?
Ambos se usan para optional binding, pero se utilizan de forma distinta.
| Feature | if let |
guard let |
|---|---|---|
| Main use | Conditional branch | Early exit |
| Scope of unwrapped value | Inside the if block |
After the guard statement |
| Readability | Good for short checks | Better for required conditions |
if let name = optionalName {
print(name)
}guard let name = optionalName else {
return
}
print(name)Usa if let para trabajo opcional dentro de una rama local.
Usa guard let cuando el valor sea necesario para el resto de la función.
25. ¿Qué es un half-open range?
Un half-open range incluye el límite inferior pero excluye el límite superior.
Usa ..<
for i in 0..<5 {
print(i)
}Salida:
0
1
2
3
4Los half-open ranges son útiles para:
- Bucles
- Indexación de arrays
- Situaciones en las que no debe incluirse el valor final
26. ¿Qué es una cláusula `where`?
Una cláusula where añade una condición extra a un patrón, un bucle, una
restricción genérica o un bloque catch.
let point = (2, 2)
switch point {
case let (x, y) where x == y:
print("x equals y")
default:
print("No match")
}for number in 1...10 where number.isMultiple(of: 2) {
print(number)
}Usa where cuando quieras un matching o un filtrado más preciso.
27. ¿Qué hace `defer`?
defer programa código para ejecutarse justo antes de que el scope actual termine.
Se ejecuta sin importar cómo termine el scope:
- Retorno normal
- Retorno anticipado
- Error lanzado
func performTask() {
print("Start")
defer {
print("Cleanup")
}
print("Work")
}Salida:
Start
Work
Cleanupdefer es útil para tareas de limpieza como:
- Cerrar archivos
- Liberar recursos bloqueados
- Restablecer estado
28. ¿Qué son `break` y `continue`?
break detiene el bucle actual o el switch.
continue omite la iteración actual del bucle y pasa a la siguiente.
for number in 1...5 {
if number == 3 {
break
}
print(number)
}for number in 1...5 {
if number == 3 {
continue
}
print(number)
}Usa break para detener el procesamiento.
Usa continue para omitir una iteración.
29. ¿Para qué se usa `stride()`?
stride() se usa para crear secuencias que avanzan por pasos.
Es útil cuando no quieres incrementar de 1 en 1.
for number in stride(from: 0, through: 10, by: 2) {
print(number)
}for number in stride(from: 0, to: 10, by: 2) {
print(number)
}Diferencia:
throughincluye el valor final si es posibletoexcluye el valor final
30. ¿Qué son los raw strings?
Los raw strings te permiten escribir literales de cadena sin tratar las barras invertidas y las comillas como caracteres de escape normales.
Usan # alrededor de la cadena.
let path = #"C:\Users\Name\Documents"#
let text = #"He said "Hello""#Si quieres interpolación dentro de un raw string, usa \#(...):
let name = "Alice"
let message = #"Hello, \#(name)"#Los raw strings son útiles para:
- Expresiones regulares
- Rutas de archivos
- Fragmentos JSON
- Cadenas con muchas barras invertidas o comillas
31. ¿Qué son los closures en Swift?
Los closures son bloques de código autocontenidos que pueden pasarse de un lugar a otro y ejecutarse más tarde.
Son similares a las funciones, pero pueden almacenarse en variables y capturar valores del scope que los rodea.
let greet = { (name: String) in
print("Hello, \(name)")
}
greet("Alice")Los closures se usan habitualmente para:
- Callbacks
- Completion handlers
- Ordenación
- Operaciones funcionales como
mapyfilter
32. ¿Escaping vs non-escaping closures?
Un non-escaping closure se ejecuta antes de que la función retorne. Un escaping closure puede almacenarse y ejecutarse después de que la función retorne.
func runNow(action: () -> Void) {
action()
}var storedAction: (() -> Void)?
func runLater(action: @escaping () -> Void) {
storedAction = action
}Usa non-escaping closures por defecto. Usa escaping closures para trabajo asíncrono, callbacks o closures almacenados.
33. ¿Qué significa `@escaping`?
@escaping significa que el closure puede sobrevivir a la función en la que fue pasado.
En otras palabras, no se garantiza que el closure se ejecute antes de que la función retorne.
class Downloader {
var completion: (() -> Void)?
func fetch(completion: @escaping () -> Void) {
self.completion = completion
}
}@escaping suele ser necesario cuando el closure:
- Se almacena en una propiedad
- Se ejecuta de forma asíncrona
- Se pasa a otra función que lo almacena
34. ¿Qué son los trailing closures?
Un trailing closure es un closure escrito después de los paréntesis de la función.
Esta sintaxis es más limpia cuando el último argumento es un closure.
func perform(action: () -> Void) {
action()
}
perform {
print("Done")
}Los trailing closures son comunes en:
- APIs asíncronas
- Callbacks de UIKit y SwiftUI
- Operaciones sobre colecciones
35. ¿Qué es value capturing en closures?
Los closures pueden capturar valores del scope que los rodea y seguir usándolos incluso después de que ese scope normalmente ya no exista.
func makeCounter() -> () -> Int {
var count = 0
return {
count += 1
return count
}
}
let counter = makeCounter()
print(counter()) // 1
print(counter()) // 2Aquí, el closure captura count y lo mantiene vivo.
Esto es útil, pero también puede provocar problemas de memoria si se capturan objetos fuertemente por error.
36. ¿Cómo gestiona Swift la memoria en los closures?
Swift usa ARC (Automatic Reference Counting). Los closures son reference types, por lo que pueden capturar objetos con una referencia fuerte.
Si un closure captura fuertemente a self, y self mantiene fuertemente al closure,
puede producirse un retain cycle.
class Example {
var completion: (() -> Void)?
func setup() {
completion = { [weak self] in
self?.doWork()
}
}
func doWork() {
print("Working")
}
}Para evitar memory leaks, usa capture lists cuando sea necesario:
[weak self]cuandoselfpueda pasar a sernil[unowned self]cuandoselfdeba seguir existiendo
37. ¿Qué son las higher-order functions (`map`, `filter`, `reduce`)?
Las higher-order functions son funciones que reciben otras funciones o closures como argumentos, o los devuelven.
En Swift, las higher-order functions más comunes sobre colecciones son:
mapfilterreduce
Transforma cada elemento.
let numbers = [1, 2, 3]
let doubled = numbers.map { $0 * 2 }Mantiene solo los elementos que cumplen la condición.
let evenNumbers = numbers.filter { $0.isMultiple(of: 2) }Combina todos los elementos en un solo resultado.
let sum = numbers.reduce(0) { $0 + $1 }Estas funciones hacen que el código sea más corto y expresivo.
38. ¿Cuál es la diferencia entre `map` y `compactMap`?
map transforma cada elemento y mantiene el mismo número de elementos.
compactMap transforma elementos y elimina los resultados nil.
let values = ["1", "two", "3"]
let mapped = values.map { Int($0) }
// [Optional(1), nil, Optional(3)]let values = ["1", "two", "3"]
let compactMapped = values.compactMap { Int($0) }
// [1, 3]Usa map cuando todos los resultados transformados deban mantenerse en la salida.
Usa compactMap cuando quieras ignorar conversiones fallidas o valores nil.
39. ¿Qué es currying (conceptualmente)?
Currying es la idea de convertir una función que recibe varios argumentos en una secuencia de funciones que reciben un argumento cada una.
Conceptualmente:
func add(_ a: Int) -> (Int) -> Int {
return { b in
a + b
}
}
let addFive = add(5)
print(addFive(3)) // 8Currying es útil para:
- Aplicación parcial
- Funciones especializadas reutilizables
- Conceptos de programación funcional
Es más un concepto que algo que se use todos los días en el código típico de aplicaciones Swift.
40. ¿Qué es `@autoclosure`?
@autoclosure envuelve automáticamente una expresión dentro de un closure.
Esto te permite pasar una expresión donde se espera un closure, sin escribir manualmente la sintaxis del closure.
func logIfTrue(_ condition: @autoclosure () -> Bool) {
if condition() {
print("True")
}
}
logIfTrue(2 > 1)Sin @autoclosure, escribirías:
logIfTrue({ 2 > 1 })Se usa con frecuencia en APIs como assert.
Úsalo con cuidado, porque puede ocultar el hecho de que se está creando un closure.
41. ¿Qué son los genéricos y por qué son útiles?
Los genéricos te permiten escribir código flexible y reutilizable que funciona con distintos tipos manteniendo la seguridad de tipos.
func swapValues<T>(_ a: inout T, _ b: inout T) {
let temp = a
a = b
b = temp
}Aquí, T es un marcador de posición para un tipo genérico.
Los genéricos son útiles porque:
- Reducen la duplicación de código
- Mantienen una fuerte seguridad de tipos
- Hacen que las APIs sean más reutilizables
42. ¿Qué son los protocolos?
Un protocolo define un conjunto de requisitos que un tipo debe cumplir.
Puede requerir:
- Propiedades
- Métodos
- Inicializadores
protocol Drivable {
func drive()
}
struct Car: Drivable {
func drive() {
print("Driving")
}
}Los protocolos se usan para definir comportamiento compartido sin forzar herencia.
43. ¿Qué es protocol-oriented programming (POP)?
Protocol-oriented programming es un enfoque en el que el comportamiento se construye usando protocolos y extensiones de protocolos en lugar de depender principalmente de la herencia de clases.
En Swift, este es un estilo de diseño central.
protocol Identifiable {
var id: String { get }
}
extension Identifiable {
func printID() {
print(id)
}
}POP es útil porque:
- Fomenta la composición sobre la herencia
- Mejora la reutilización de código
- Funciona bien con value types como
struct
44. ¿Qué son los associated types?
Un associated type es un tipo marcador de posición dentro de un protocolo.
Permite que el tipo que adopta el protocolo decida qué tipo concreto se usará.
protocol Container {
associatedtype Item
var items: [Item] { get set }
}
struct IntContainer: Container {
var items: [Int]
}Los associated types son útiles cuando un protocolo debe trabajar con distintos tipos de datos, pero seguir siendo type-safe.
45. ¿Qué es protocol composition?
Protocol composition significa combinar varios protocolos en un único requisito de tipo.
Usa &.
protocol Readable {}
protocol Writable {}
func process(file: Readable & Writable) {
print("Can read and write")
}Usa protocol composition cuando un valor deba cumplir varios comportamientos al mismo tiempo.
46. ¿Qué son las protocol extensions?
Las protocol extensions te permiten añadir implementaciones por defecto a los protocolos.
protocol Greetable {
var name: String { get }
func greet()
}
extension Greetable {
func greet() {
print("Hello, \(name)")
}
}Esto significa que los tipos conformes pueden obtener comportamiento compartido automáticamente.
Las protocol extensions son una parte clave de protocol-oriented programming.
47. ¿Qué es conditional conformance?
Conditional conformance significa que un tipo genérico conforma a un protocolo solo cuando se cumplen ciertas condiciones.
extension Array: Equatable where Element: Equatable {}Esto significa que Array es Equatable solo si sus elementos son Equatable.
Es útil para construir APIs genéricas que sigan siendo precisas y type-safe.
48. ¿Qué es type erasure?
Type erasure oculta un tipo concreto específico detrás de un wrapper común o de una interfaz abstracta.
A menudo es necesario al trabajar con protocolos que tienen:
- Associated types
- Requisitos de
Self
Por ejemplo, SwiftUI usa AnyView como un wrapper con type erasure.
let views: [AnyView] = [
AnyView(Text("Hello")),
AnyView(Image(systemName: "star"))
]Type erasure ayuda a almacenar o pasar valores con distintos tipos concretos de una forma uniforme.
49. ¿Cuál es la diferencia entre `Self` y `self`?
Self y self son diferentes.
Selfse refiere al propio tiposelfse refiere a la instancia actual
struct User {
var name: String
func printName() {
print(self.name)
}
}protocol Copyable {
func copy() -> Self
}Usa self cuando trabajes con el objeto o valor actual.
Usa Self cuando te refieras al tipo concreto a nivel de tipo.
50. ¿Cómo funciona ARC en Swift?
ARC significa Automatic Reference Counting.
Es el sistema de gestión de memoria de Swift para instancias de clases. ARC realiza un seguimiento automático de cuántas referencias fuertes apuntan a un objeto.
- Cuando el recuento de referencias aumenta, el objeto permanece en memoria
- Cuando el recuento de referencias baja a cero, el objeto se libera
class Person {
let name: String
init(name: String) {
self.name = name
print("\(name) initialized")
}
deinit {
print("\(name) deallocated")
}
}ARC no gestiona value types como struct y enum de la misma manera,
porque no son reference types.
El principal riesgo de ARC es un retain cycle, que normalmente se resuelve con
referencias weak o unowned.
51. ¿Qué es un retain cycle?
Un retain cycle ocurre cuando dos o más objetos de tipo referencia mantienen referencias fuertes entre sí, de modo que ninguno puede liberarse.
Esto normalmente causa un memory leak.
class Person {
var apartment: Apartment?
}
class Apartment {
var tenant: Person?
}Si ambas referencias son fuertes, los objetos se mantienen vivos mutuamente.
Los retain cycles son comunes con:
- Closures que capturan
self - Relaciones padre-hijo entre objetos
- Delegate patterns implementados incorrectamente
52. ¿Cuál es la diferencia entre `weak` y `unowned`?
Tanto weak como unowned se usan para evitar retain cycles, pero se
comportan de forma distinta.
| Feature | weak |
unowned |
|---|---|---|
| Optional | Yes | No |
Can become nil |
Yes | No |
| Safety | Safer | Less safe |
| Use when | Referenced object may disappear | Referenced object must exist |
weak var delegate: SomeDelegate?
unowned var owner: OwnerUsa weak cuando la referencia pueda convertirse en nil.
Usa unowned cuando la referencia siempre deba apuntar a un objeto válido.
53. ¿Cuándo usarías `unowned` en lugar de `weak`?
Usa unowned cuando el objeto referenciado deba existir siempre mientras se use esta referencia.
Esto es común cuando dos objetos están vinculados, pero uno posee claramente el ciclo de vida del otro.
class Customer {
let name: String
init(name: String) {
self.name = name
}
}
class CreditCard {
unowned let customer: Customer
init(customer: Customer) {
self.customer = customer
}
}Usa unowned solo cuando estés seguro de que nunca se accederá a la referencia
después de que el objeto se haya liberado.
En caso contrario, usa weak.
54. ¿Qué es un memory leak?
Un memory leak ocurre cuando la memoria ya no es necesaria, pero aun así no se libera.
En Swift, esto suele ocurrir porque los objetos siguen teniendo referencias fuertes, a menudo debido a retain cycles.
Señales de un memory leak:
- Los objetos nunca se liberan
- El uso de memoria sigue creciendo
- Pantallas o view models permanecen en memoria después de cerrarse
Los memory leaks pueden provocar:
- Mayor uso de memoria
- Problemas de rendimiento
- Terminación de la app por parte del sistema
55. ¿Cómo depuras problemas de memoria?
Las formas más comunes de depurar problemas de memoria en Swift son:
- Xcode Memory Graph Debugger
- Instruments con la herramienta Leaks
- Instruments con Allocations
- Comprobar si se llama a
deinit
deinit {
print("Deallocated")
}Si deinit no se llama cuando debería, algo sigue reteniendo el objeto.
Un flujo de trabajo habitual es:
- Reproducir el flujo de la pantalla
- Cerrar la pantalla
- Revisar Memory Graph
- Buscar retain cycles o referencias fuertes inesperadas
56. ¿Cómo gestiona Swift la memoria para value types frente a reference types?
Swift gestiona la memoria de forma diferente para value types y reference types.
structenumtuple
Se copian al asignarse o al pasarse a funciones. No usan ARC de la misma manera que las instancias de clases.
class
Se comparten por referencia. Swift usa ARC para gestionar su ciclo de vida.
Así que la diferencia principal es:
- Los value types copian datos
- Los reference types comparten la misma instancia
57. ¿Qué es async/await en Swift?
async/await es la sintaxis moderna de Swift para programación asíncrona.
Hace que el código asíncrono se parezca más a código secuencial normal.
func loadUser() async throws -> String {
return "Alice"
}
func fetchData() async {
do {
let user = try await loadUser()
print(user)
} catch {
print(error)
}
}Beneficios:
- Código asíncrono más limpio
- Mejor legibilidad
- Manejo de errores más sencillo con
try
58. ¿Qué es structured concurrency?
Structured concurrency significa que el trabajo asíncrono se organiza en una jerarquía clara de tareas padre e hijas.
Esto ayuda a Swift a gestionar:
- El ciclo de vida de las tareas
- La cancelación
- La propagación de errores
Con structured concurrency, las tareas hijas normalmente pertenecen al scope donde se crearon y no quedan ejecutándose sin control.
Las herramientas comunes de structured concurrency son:
async letTaskGroup
Hace que el código concurrente sea más seguro y más fácil de razonar.
59. ¿Qué son Tasks y Task groups?
Una Task es una unidad de trabajo asíncrono.
Un TaskGroup te permite ejecutar varias tareas hijas en paralelo y recoger sus resultados.
Task {
print("Running async work")
}await withTaskGroup(of: Int.self) { group in
group.addTask { 1 }
group.addTask { 2 }
for await value in group {
print(value)
}
}Usa:
Taskpara una operación asíncronaTaskGrouppara varias tareas hijas en paralelo
60. ¿Qué es `MainActor` y por qué es importante?
MainActor es un actor global que garantiza que el código se ejecute en el hilo principal.
Es importante porque las actualizaciones de UI deben hacerse en el hilo principal.
@MainActor
class ViewModel {
var title = ""
func updateTitle() {
title = "Updated"
}
}También puedes cambiar a él explícitamente:
await MainActor.run {
print("Update UI")
}MainActor ayuda a evitar errores de threading y hace más seguro el código relacionado con la UI.
61. ¿Qué son los actors y cómo previenen race conditions?
Los actors son reference types que protegen su estado mutable frente a accesos concurrentes inseguros.
Ayudan a prevenir race conditions aislando el estado, de modo que solo una tarea puede acceder a ese estado mutable a la vez.
actor Counter {
private var value = 0
func increment() {
value += 1
}
func getValue() -> Int {
value
}
}Para acceder al estado aislado por un actor desde fuera, normalmente se usa await.
Los actors son importantes en la concurrencia moderna de Swift porque ofrecen una alternativa más segura que los locks manuales en muchos casos.
62. ¿Qué es un data race y cómo lo previene Swift?
Un data race ocurre cuando varios hilos o tareas acceden al mismo dato mutable al mismo tiempo y al menos uno de ellos lo escribe.
Esto puede causar comportamiento impredecible y crashes.
Swift ayuda a prevenir data races con:
- Aislamiento por actors
Sendable- Structured concurrency
- Aislamiento del main actor para código de UI
En Swift moderno, las características de concurrencia están diseñadas para detectar antes el estado mutable compartido inseguro y facilitar su prevención.
63. ¿Cuál es la diferencia entre GCD y la concurrencia moderna?
Tanto GCD como la concurrencia moderna de Swift manejan trabajo asíncrono, pero usan modelos diferentes.
| Feature | GCD | Modern concurrency |
|---|---|---|
| Style | Callback-based | async/await |
| Readability | More nested | More linear |
| Cancellation | Manual | Built-in support |
| Safety | Lower-level | Safer and more structured |
| Tools | DispatchQueue |
Task, actors, TaskGroup, MainActor |
DispatchQueue.global().async {
print("Background work")
}Task {
print("Async work")
}La concurrencia moderna suele ser la opción preferida para nuevo código Swift.
64. ¿Cómo sincronizas el acceso al estado compartido?
Sincronizas el estado compartido asegurándote de que el código concurrente no lea y escriba los mismos datos mutables de forma insegura.
Enfoques comunes:
- Actors
- Colas seriales
- Locks
MainActorpara estado de UI
actor Store {
private var items: [String] = []
func add(_ item: String) {
items.append(item)
}
}En Swift moderno, los actors suelen ser la opción más segura por defecto para estado mutable compartido.
65. ¿Qué es Sendable en Swift?
Sendable es un protocolo que marca un tipo como seguro para transferirse
entre dominios de concurrencia.
Esto importa cuando se pasan valores entre tareas o actors.
struct User: Sendable {
let id: Int
let name: String
}Los value types con propiedades almacenadas inmutables suelen ser
naturalmente Sendable.
Sendable ayuda a Swift a detectar el intercambio inseguro de estado mutable
en código concurrente.
66. ¿Cómo funciona el manejo de errores en Swift?
Swift usa manejo de errores tipado con throw, throws, do, try y catch.
enum LoginError: Error {
case invalidCredentials
}
func login(success: Bool) throws {
if !success {
throw LoginError.invalidCredentials
}
}
do {
try login(success: false)
} catch {
print(error)
}Esto hace que el manejo de errores sea explícito y más seguro que ignorar fallos silenciosamente.
67. ¿Cuál es la diferencia entre `try`, `try?` y `try!`?
Los tres se usan con funciones que pueden lanzar errores, pero los manejan de forma distinta.
| Keyword | Behavior |
|---|---|
try |
Propagates or handles the error normally |
try? |
Converts the result to an optional and returns nil on error |
try! |
Forces success and crashes if an error is thrown |
let value1 = try someThrowingFunction()
let value2 = try? someThrowingFunction()
let value3 = try! someThrowingFunction()Usa:
trypara el manejo normal de errorestry?cuando el fallo pueda ignorarse comoniltry!solo cuando el fallo sea imposible
68. ¿Qué es `throws` frente a `rethrows`?
throws significa que una función puede lanzar su propio error.
rethrows significa que una función solo lanza si uno de sus argumentos
función lanza un error.
func load() throws {
// may throw
}func perform(_ action: () throws -> Void) rethrows {
try action()
}Usa rethrows para funciones wrapper que no lanzan por sí mismas, pero llaman
a un closure que sí puede lanzar.
69. ¿Qué es el tipo Result?
Result es un enum que representa:
- Éxito con un valor
- Fallo con un error
let result: Result<String, Error> = .success("Done")Está definido aproximadamente así:
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}Result es útil cuando quieres pasar explícitamente éxito o fallo,
especialmente en callbacks o APIs asíncronas.
70. ¿Cómo propagas errores?
Propagas errores marcando una función con throws y usando try al llamar a otra función que lanza errores.
func loadData() throws {
throw NSError(domain: "Example", code: 1)
}
func processData() throws {
try loadData()
}Aquí, processData() no maneja el error por sí misma.
Pasa el error a quien la llama.
Esto es útil cuando una función de nivel inferior debe informar del fallo y dejar que una capa de nivel superior decida cómo manejarlo.
71. ¿Qué son los property wrappers?
Los property wrappers son una característica de Swift que te permite añadir comportamiento reutilizable alrededor de stored properties.
Se declaran con @propertyWrapper.
@propertyWrapper
struct Uppercased {
private var value: String = ""
var wrappedValue: String {
get { value }
set { value = newValue.uppercased() }
}
}
struct User {
@Uppercased var name: String
}Los property wrappers son útiles para:
- Validación
- Formateo
- Persistencia
- Inyección de dependencias
72. ¿Qué son los opaque return types (`some`)?
Los opaque return types permiten que una función devuelva un valor de algún tipo específico sin exponer ese tipo exacto en la API.
Usan la palabra clave some.
func makeView() -> some View {
Text("Hello")
}Quien llama a la función sabe que el valor devuelto conforma a View, pero no
necesita conocer el tipo concreto.
Los opaque types se usan mucho en SwiftUI.
73. ¿Cuál es la diferencia entre opaque types y genéricos?
Tanto los opaque types como los genéricos trabajan con tipos abstractos, pero resuelven problemas diferentes.
| Feature | Generics | Opaque types |
|---|---|---|
| Who chooses the concrete type? | Caller | Implementation |
| Syntax | <T> |
some Protocol |
| Main use | Flexible reusable APIs | Hiding concrete return types |
func echo<T>(_ value: T) -> T {
value
}func makeView() -> some View {
Text("Hello")
}Usa genéricos cuando el tipo lo proporcione quien llama. Usa opaque types cuando el tipo lo proporcione la implementación, pero lo oculte.
74. ¿Qué son los result builders?
Los result builders son una característica que permite a Swift construir valores complejos a partir de un bloque de código declarativo.
Se usan mucho en SwiftUI.
@resultBuilder
struct StringBuilder {
static func buildBlock(_ components: String...) -> String {
components.joined(separator: " ")
}
}Un result builder transforma varias expresiones en un único resultado final.
Esto es útil para APIs de estilo DSL.
75. ¿Qué son los custom operators?
Los custom operators te permiten definir tus propios operadores en Swift.
infix operator +++
func +++ (lhs: Int, rhs: Int) -> Int {
lhs + rhs + 1
}Puedes crear:
- Operadores prefix
- Operadores infix
- Operadores postfix
Pueden ser potentes, pero deben usarse con cuidado.
Si se abusa de ellos, el código se vuelve más difícil de leer.
76. ¿Qué es dynamic dispatch frente a static dispatch?
Dispatch es la forma en que Swift decide qué implementación de método llamar.
La llamada al método se resuelve en tiempo de compilación. Normalmente es más rápido.
Esto es común con:
structenum- Métodos
final
La llamada al método se resuelve en tiempo de ejecución. Es más flexible, pero tiene más overhead.
Esto es común con:
- Clases con herencia
@objc dynamic
Static dispatch se centra en el rendimiento. Dynamic dispatch se centra en la flexibilidad en tiempo de ejecución.
77. ¿Qué es un phantom type?
Un phantom type es un parámetro de tipo genérico que se usa solo en tiempo de compilación y no se almacena en tiempo de ejecución.
Ayuda a hacer las APIs más seguras codificando información extra en el sistema de tipos.
struct ID<Tag> {
let value: String
}
enum UserTag {}
enum OrderTag {}
let userID = ID<UserTag>(value: "123")
let orderID = ID<OrderTag>(value: "123")Aunque ambos valores contienen un String, Swift los trata como tipos diferentes.
78. ¿Qué es escape analysis?
Escape analysis es un concepto de optimización del compilador que se usa para determinar si un valor o closure escapa del scope donde fue creado.
Si algo no escapa, Swift puede optimizar el uso de memoria y el comportamiento de llamada de forma más eficiente.
Este concepto está relacionado con:
- Escaping vs non-escaping closures
- Decisiones de optimización entre stack y heap
En entrevistas habituales de Swift, normalmente basta con saber que los non-escaping closures son más fáciles de optimizar para el compilador.
79. ¿Qué es function builder (result builder)?
Function builder es el nombre antiguo de lo que Swift ahora llama result builder.
Es una característica que transforma un bloque de expresiones en un único resultado, a menudo para APIs declarativas.
SwiftUI usa esto intensamente con su sintaxis de construcción de vistas.
@ViewBuilder
func makeContent(isLoggedIn: Bool) -> some View {
if isLoggedIn {
Text("Welcome")
} else {
Text("Please log in")
}
}Así que hoy:
function builderes el término antiguoresult builderes el término oficial actual
80. ¿Qué es SwiftUI y en qué se diferencia de UIKit?
SwiftUI es el framework declarativo de Apple para construir interfaces en las plataformas Apple.
UIKit es el framework imperativo más antiguo para construir interfaces en iOS.
| Feature | SwiftUI | UIKit |
|---|---|---|
| Style | Declarative | Imperative |
| UI updates | State-driven | Manual updates |
| Syntax | Swift-based DSL | Classes and lifecycle APIs |
| Typical usage | Modern Apple UI development | Mature iOS UI framework |
struct ContentView: View {
var body: some View {
Text("Hello")
}
}let label = UILabel()
label.text = "Hello"SwiftUI suele ofrecer un desarrollo de UI más rápido y una mejor integración con las características modernas de Swift. UIKit ofrece un control de más bajo nivel y tiene un ecosistema legacy más grande.
81. ¿Qué es la UI declarativa?
La UI declarativa significa que describes cómo debe verse la interfaz para un estado determinado, en lugar de escribir instrucciones paso a paso para actualizarla.
En SwiftUI, declaras la interfaz basándote en los datos, y el framework actualiza la pantalla cuando cambia el estado.
struct ContentView: View {
let isLoggedIn: Bool
var body: some View {
if isLoggedIn {
Text("Welcome")
} else {
Text("Please log in")
}
}
}Esto es diferente de la UI imperativa, donde creas vistas manualmente y las modificas directamente.
82. ¿Qué es `@State`?
@State es un property wrapper usado en SwiftUI para almacenar estado mutable local dentro de una vista.
Cuando un valor @State cambia, SwiftUI vuelve a renderizar la vista.
struct CounterView: View {
@State private var count = 0
var body: some View {
Button("Count: \(count)") {
count += 1
}
}
}Usa @State para estado simple que pertenece a la propia vista.
83. ¿Qué es `@StateObject` frente a `@ObservedObject`?
Ambos se usan con tipos referencia observables, pero se diferencian en la propiedad del objeto.
| Wrapper | Ownership |
|---|---|
@StateObject |
The view creates and owns the object |
@ObservedObject |
The object is created elsewhere and passed into the view |
struct ParentView: View {
@StateObject private var viewModel = UserViewModel()
}struct ChildView: View {
@ObservedObject var viewModel: UserViewModel
}Usa @StateObject cuando la vista deba mantener vivo el objeto.
Usa @ObservedObject cuando la vista solo observe un objeto externo.
84. ¿Qué es `@EnvironmentObject`?
@EnvironmentObject se usa para compartir un objeto observable a través de la
jerarquía de vistas de SwiftUI sin pasarlo manualmente por cada vista.
class AppState: ObservableObject {
@Published var isLoggedIn = false
}
struct ContentView: View {
@EnvironmentObject var appState: AppState
}Es útil para estado compartido a nivel de app, como:
- Sesión de usuario
- Tema
- Ajustes
El objeto debe inyectarse en el environment antes de que la vista lo use.
85. ¿Qué es `@Published`?
@Published es un property wrapper usado dentro de un ObservableObject.
Cuando la propiedad cambia, notifica a SwiftUI u otros suscriptores que el objeto ha cambiado.
class UserViewModel: ObservableObject {
@Published var name = "Alice"
}@Published se usa habitualmente para estado de view model que debe actualizar la UI.
86. ¿Cómo funciona la gestión de estado en SwiftUI?
La gestión de estado en SwiftUI se basa en que los datos impulsan la UI.
Cuando el estado cambia, SwiftUI recalcula las vistas afectadas.
Herramientas comunes:
@Statepara estado local de la vista@StateObjectpara objetos observables propios@ObservedObjectpara objetos observables externos@EnvironmentObjectpara objetos compartidos a nivel de app@Bindingpara pasar estado mutable entre vistas
La idea central es simple:
- El estado cambia
- SwiftUI actualiza la jerarquía de vistas
87. ¿Qué es un `ViewModifier`?
Un ViewModifier es una forma reutilizable de aplicar estilo o comportamiento
a vistas de SwiftUI.
struct TitleStyle: ViewModifier {
func body(content: Content) -> some View {
content
.font(.title)
.foregroundColor(.blue)
}
}Uso:
Text("Hello").modifier(TitleStyle())Los view modifiers ayudan a evitar repetir la misma lógica de estilo de vistas.
88. ¿Qué es el environment de SwiftUI?
El environment de SwiftUI es un sistema para pasar valores por la jerarquía de vistas de forma implícita.
Proporciona contexto compartido como:
- Esquema de color
- Locale
- Categoría de tamaño
- Valores personalizados de environment
Lees valores del environment con @Environment.
struct ContentView: View {
@Environment(\.colorScheme) var colorScheme
}El environment ayuda a evitar pasar manualmente configuración común por muchas capas de vistas.
89. ¿Qué es `GeometryReader`?
GeometryReader es un contenedor de SwiftUI que da acceso a información de
layout sobre el espacio disponible y la geometría de la vista.
GeometryReader { geometry in
Text("Width: \(geometry.size.width)")
}Es útil cuando necesitas:
- Layout dinámico según el tamaño disponible
- Cálculos de posición
- Comportamiento responsive de la UI
Debe usarse con cuidado, porque un uso excesivo puede hacer más difícil razonar sobre el layout.
90. ¿Cómo funciona la navegación en SwiftUI?
La navegación en SwiftUI normalmente se construye con NavigationStack.
Navegas haciendo push de destinos sobre una pila.
NavigationStack {
NavigationLink("Open Details") {
DetailView()
}
}También puedes usar navegación basada en valores con navigationDestination.
NavigationStack {
List(["A", "B"], id: \.self) { item in
NavigationLink(value: item) {
Text(item)
}
}
.navigationDestination(for: String.self) { item in
Text("Selected: \(item)")
}
}La navegación en SwiftUI está impulsada por estado y es más declarativa que los navigation controllers de UIKit.
91. ¿Cuál es el lifecycle de una vista SwiftUI?
Las vistas de SwiftUI son value types, así que su lifecycle es diferente al de los view controllers de UIKit.
Una vista SwiftUI se crea y recrea con frecuencia a medida que cambia el estado. La idea importante no es la vida del struct en sí, sino la vida del estado conectado a él.
Los hooks de lifecycle más comunes incluyen:
initbodyonAppearonDisappear.task
struct ContentView: View {
var body: some View {
Text("Hello")
.onAppear {
print("Appeared")
}
.onDisappear {
print("Disappeared")
}
}
}En SwiftUI, el estado y el flujo de datos importan más que la vida del objeto.
92. ¿Por qué las vistas de SwiftUI son structs?
Las vistas de SwiftUI son structs porque los value types funcionan bien con una UI declarativa.
Beneficios:
- Ligeras
- Fáciles de recrear
- Flujo de datos más seguro
- Mejores oportunidades de rendimiento para SwiftUI
Como son value types, SwiftUI puede reconstruir los valores de vista con frecuencia y compararlos eficientemente cuando cambia el estado.
Las partes persistentes reales de la UI las gestiona el framework, no el propio struct de la vista.
93. ¿Cuál es la diferencia entre UIKit y SwiftUI?
UIKit y SwiftUI son frameworks de UI de Apple, pero usan enfoques diferentes.
| Feature | UIKit | SwiftUI |
|---|---|---|
| Style | Imperative | Declarative |
| Main building blocks | UIView, UIViewController |
View |
| State updates | Manual | State-driven |
| Typical usage | Mature, lower-level UI control | Modern declarative UI development |
UIKit te da un control más directo. SwiftUI te ofrece una forma más moderna y concisa de construir UI.
94. ¿Qué es Auto Layout?
Auto Layout es el sistema de Apple para posicionar y dimensionar elementos de UI usando constraints.
En lugar de establecer frames manualmente para cada tamaño de pantalla, defines reglas sobre cómo se relacionan las vistas entre sí.
Ejemplos:
- Un label está a 16 puntos del borde izquierdo
- Un botón está centrado horizontalmente
- Una imagen mantiene el mismo ancho y alto
Auto Layout ayuda a construir interfaces adaptativas para diferentes dispositivos y tamaños de pantalla.
95. ¿Qué es intrinsic content size?
Intrinsic content size es el tamaño natural que una vista prefiere según su contenido.
Por ejemplo:
- Un label dimensiona su tamaño según su texto
- Un image view dimensiona su tamaño según la imagen
Esto ayuda a Auto Layout a saber qué tamaño debe tener una vista incluso sin constraints explícitas de ancho y alto.
Vistas como UILabel y UIButton suelen tener intrinsic content size.
96. ¿Qué son constraints y anchors?
Las constraints son reglas de layout usadas por Auto Layout. Los anchors son una forma más cómoda en código de crear esas constraints.
Anchors comunes:
topAnchorbottomAnchorleadingAnchortrailingAnchorwidthAnchorheightAnchor
button.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([
button.centerXAnchor.constraint(equalTo: view.centerXAnchor),
button.topAnchor.constraint(equalTo: view.topAnchor, constant: 20)
])Los anchors hacen que el código de Auto Layout sea más seguro y más fácil de leer que las APIs de constraints más antiguas.
97. ¿Qué son las reusable cells?
Las reusable cells son celdas de table view o collection view que se reutilizan en lugar de crearse desde cero para cada elemento.
Esto mejora:
- El rendimiento
- El uso de memoria
- La eficiencia del scroll
let cell = tableView.dequeueReusableCell(withIdentifier: "Cell", for: indexPath)La reutilización funciona reciclando celdas fuera de pantalla para nuevo contenido.
98. ¿Cuál es la diferencia entre UITableView y UICollectionView?
Ambos muestran listas de datos, pero están diseñados para distintas necesidades de layout.
| Feature | UITableView |
UICollectionView |
|---|---|---|
| Layout style | Mostly vertical list | Flexible layouts |
| Complexity | Simpler | More customizable |
| Typical use | Simple lists, forms, settings | Grids, custom layouts, complex lists |
Usa UITableView para una UI sencilla basada en listas.
Usa UICollectionView cuando necesites layouts más flexibles o personalizados.
99. ¿Qué es `@IBOutlet` frente a `@IBAction`?
Ambos se usan con Interface Builder en storyboards UIKit o archivos XIB.
@IBOutletconecta un elemento de UI con el código@IBActionconecta un evento de UI con el código
@IBOutlet weak var titleLabel: UILabel!@IBAction func buttonTapped(_ sender: UIButton) {
print("Tapped")
}@IBOutlet sirve para referencias a elementos de UI.
@IBAction sirve para manejar acciones del usuario.
100. ¿Qué es el lifecycle de `UIViewController`?
El lifecycle de UIViewController es la secuencia de métodos que se llaman
cuando un view controller se crea, se muestra, se layouta y se retira.
Métodos de lifecycle comunes:
viewDidLoadviewWillAppearviewDidAppearviewWillDisappearviewDidDisappear
viewDidLoad: configuración inicialviewWillAppear: actualizar la UI antes de que se vuelva visibleviewDidAppear: iniciar animaciones o trackingviewWillDisappear: prepararse para salir de la pantallaviewDidDisappear: trabajo de limpieza
Comprender este lifecycle es esencial en el desarrollo de apps UIKit.
101. ¿Qué es MVC?
MVC significa Model-View-Controller.
Es un patrón de diseño que separa una app en tres partes:
- Model: datos y lógica de negocio
- View: UI
- Controller: gestiona la interacción entre el modelo y la vista
En iOS, UIViewController suele desempeñar el papel de controller.
MVC es simple y común, pero en apps UIKit puede llevar fácilmente a un "Massive View Controller" si se coloca demasiada lógica en el controller.
102. ¿Qué es MVVM?
MVVM significa Model-View-ViewModel.
Separa las responsabilidades así:
- Model: datos y reglas de negocio
- View: UI
- ViewModel: lógica de presentación y estado para la vista
El ViewModel prepara los datos para mostrarlos y reduce la lógica dentro de la vista o del view controller.
MVVM es popular en SwiftUI y también se usa con frecuencia en apps UIKit.
103. ¿Qué es dependency injection?
Dependency injection significa proporcionar a un objeto las dependencias que necesita desde fuera, en lugar de crearlas dentro del propio objeto.
Por ejemplo, en vez de que un view model cree su propio servicio de red, el servicio se le pasa desde fuera.
Beneficios:
- Mejor testabilidad
- Menor acoplamiento
- Sustitución más fácil de implementaciones
104. ¿Cómo implementas DI en Swift?
La forma más común es la inyección por constructor.
protocol NetworkService {
func fetchData()
}
final class ViewModel {
private let service: NetworkService
init(service: NetworkService) {
self.service = service
}
}Otros enfoques comunes:
- Inyección por propiedad
- Inyección por método
- Contenedores de dependencias
La inyección por constructor suele preferirse porque hace explícitas las dependencias.
105. ¿Qué es el delegation pattern?
La delegación es un patrón en el que un objeto pasa la responsabilidad de cierto trabajo o eventos a otro objeto.
Normalmente se implementa con protocolos.
protocol ButtonHandler: AnyObject {
func didTapButton()
}La delegación se usa mucho en UIKit, por ejemplo con:
UITableViewDelegateUITextFieldDelegate
Ayuda a mantener separadas las responsabilidades y a que la comunicación sea flexible.
106. ¿Cuándo deberías usar un singleton?
Un singleton debería usarse cuando debe existir exactamente una única instancia compartida en la app.
Ejemplos típicos:
- Configuración global de la app
- Servicio de logging
- Gestor de caché
final class Logger {
static let shared = Logger()
private init() {}
}Usa singletons con cuidado. Abusar de ellos puede crear dependencias ocultas y dificultar las pruebas.
107. ¿Qué es KVO?
KVO significa Key-Value Observing.
Es un mecanismo para observar cambios en propiedades de ciertos objetos, especialmente aquellos compatibles con Objective-C.
Está principalmente asociado con Cocoa y con el comportamiento del runtime de Objective-C.
KVO se usa menos en código Swift moderno en comparación con:
- Combine
@Published- Herramientas de estado de SwiftUI
Pero todavía aparece en algunas APIs de UIKit y Foundation.
108. ¿Qué es NotificationCenter?
NotificationCenter es un sistema para difundir mensajes a múltiples observadores.
Un objeto publica una notificación y otros objetos pueden escucharla.
NotificationCenter.default.post(name: .didLogout, object: nil)Es útil para una comunicación flexible entre partes de la app, especialmente cuando las referencias directas no son ideales.
Úsalo con cuidado, porque un uso excesivo puede hacer más difícil seguir el flujo de datos.
109. ¿Cómo haces una petición de red en Swift?
La forma estándar es usar URLSession.
let url = URL(string: "https://example.com")!
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
print(error)
return
}
if let data = data {
print(data)
}
}.resume()En Swift moderno, también puedes usar async/await con URLSession.
let (data, response) = try await URLSession.shared.data(from: url)110. ¿Qué es Codable?
Codable es un type alias para:
EncodableDecodable
Permite convertir tipos Swift fácilmente hacia y desde formatos como JSON.
struct User: Codable {
let id: Int
let name: String
}Casos de uso comunes:
- Parsear respuestas de API
- Guardar datos locales
- Codificar cuerpos de petición
Codable reduce mucho el boilerplate de serialización.
111. ¿Qué son las decoding strategies?
Las decoding strategies son opciones usadas por JSONDecoder para controlar
cómo se decodifican los datos entrantes.
Las estrategias comunes incluyen:
dateDecodingStrategykeyDecodingStrategydataDecodingStrategy
let decoder = JSONDecoder()
decoder.keyDecodingStrategy = .convertFromSnakeCase
decoder.dateDecodingStrategy = .iso8601Son útiles cuando el formato de la API no coincide directamente con el formato de tu modelo Swift.
112. ¿Qué es URLSession?
URLSession es la API principal de Apple para realizar peticiones de red.
Se usa para:
- Descargar datos
- Subir datos
- Obtener archivos
- Gestionar tareas de red
Soporta tanto el estilo completion-handler como async/await.
let (data, response) = try await URLSession.shared.data(from: url)113. ¿Qué es una estrategia de caché?
Una estrategia de caché es el plan para decidir qué datos almacenar en caché, dónde almacenarlos y cuándo actualizarlos o invalidarlos.
Capas de caché comunes en apps iOS:
- Caché en memoria
- Caché en disco
- Caché HTTP
Las decisiones clave suelen incluir:
- Vida útil de la caché
- Reglas de invalidación
- Trade-offs entre memoria y disco
- Frescura frente a velocidad
Una buena estrategia de caché mejora el rendimiento, reduce el uso de red y hace que la app se sienta más rápida.
114. ¿Cómo depuras crashes?
Las formas comunes de depurar crashes incluyen:
- Leer el crash log
- Revisar el stack trace
- Reproducir el problema localmente
- Usar el debugger de Xcode y breakpoints
- Usar herramientas de crash reporting como Firebase Crashlytics
Cosas importantes a revisar:
- El hilo que falló
- El tipo de excepción
- Los últimos métodos llamados
- El dispositivo y la versión del sistema operativo
El objetivo es reproducir el crash, identificar la causa raíz y confirmar el fix.
115. ¿Cómo detectas memory leaks?
Las formas comunes de detectar memory leaks son:
- Xcode Memory Graph Debugger
- Herramienta Leaks de Instruments
- Allocations de Instruments
- Comprobar
deinit
deinit {
print("Object deallocated")
}Si un objeto debería desaparecer pero permanece en memoria, puede haber un retain cycle u otra referencia fuerte inesperada.
116. ¿Cómo mejoras el rendimiento en apps iOS?
Las formas comunes de mejorar el rendimiento incluyen:
- Reducir el trabajo en el main thread
- Optimizar el renderizado y el scroll
- Evitar asignaciones innecesarias
- Poner en caché resultados costosos
- Usar trabajo en background para tareas pesadas
- Perfilar con Instruments
Primero debes medir y después optimizar el verdadero cuello de botella.
Herramientas típicas:
- Time Profiler
- Allocations
- Memory Graph
- Instrumentos de red
117. ¿Cómo optimizas el uso de batería?
Para optimizar el uso de batería, reduce el trabajo innecesario y evita mantener el hardware o las tareas en background activos más tiempo del necesario.
Prácticas comunes:
- Minimizar la actividad en background
- Evitar actualizaciones de ubicación excesivas
- Agrupar peticiones de red
- Reducir timers frecuentes y polling
- Evitar animaciones y redraws innecesarios
La regla principal es simple:
- Hacer menos trabajo
- Hacerlo con menos frecuencia
118. ¿Qué es Keychain y cuándo usarlo?
Keychain es el sistema de almacenamiento seguro de Apple para pequeñas piezas de datos sensibles.
Usa Keychain para cosas como:
- Tokens
- Contraseñas
- Credenciales
- Secretos sensibles
No lo uses como base de datos general.
Keychain es más seguro que UserDefaults para datos confidenciales.
119. ¿Qué es App Transport Security?
App Transport Security (ATS) es una función de seguridad de iOS que fomenta por defecto el uso de conexiones de red seguras.
En general exige:
- HTTPS
- Configuraciones TLS fuertes
Si una app necesita HTTP inseguro o configuraciones de seguridad más débiles, deben añadirse excepciones explícitas en la configuración de la app.
ATS ayuda a proteger el tráfico de red frente a intercepciones y transporte inseguro.
120. ¿Cómo almacenas datos de forma segura?
Depende de la sensibilidad de los datos.
Reglas típicas:
- Usa Keychain para contraseñas, tokens y secretos
- Usa protección de archivos para archivos sensibles
- Evita almacenar secretos en texto plano
- No almacenes datos confidenciales en
UserDefaults - Cifra los datos cuando sea necesario
También es importante:
- Limitar lo que almacenas
- Guardarlo solo el tiempo necesario
- Protegerlo tanto en reposo como en tránsito
121. ¿Qué es XCTest?
XCTest es el framework de Apple para escribir y ejecutar tests en Xcode.
Se usa para:
- Unit tests
- UI tests
- Tests de rendimiento
func testAddition() {
XCTAssertEqual(2 + 2, 4)
}Es la herramienta estándar de testing para el desarrollo en plataformas Apple.
122. ¿Qué es unit testing frente a UI testing?
Unit testing comprueba pequeñas piezas de lógica de forma aislada. UI testing comprueba la app a través de la interfaz de usuario.
| Type | Focus |
|---|---|
| Unit test | Lógica de negocio, funciones, clases |
| UI test | Flujos reales de usuario e interacciones con pantallas |
Los unit tests suelen ser:
- Más rápidos
- Más aislados
- Más fáciles de depurar
Los UI tests son útiles para validar el comportamiento end-to-end.
123. ¿Qué es mocking?
Mocking significa reemplazar una dependencia real por una versión falsa para tests.
Esto ayuda a probar una unidad de forma aislada.
Por ejemplo, en vez de llamar a una API real, un mock network service devuelve datos predefinidos.
Mocking es útil para:
- Tests más rápidos
- Resultados predecibles
- Probar escenarios de fallo
124. ¿Cómo te mantienes al día con Swift?
Una buena respuesta consiste en combinar fuentes oficiales con aprendizaje práctico.
Formas comunes:
- Leer propuestas de Swift Evolution
- Seguir sesiones de WWDC
- Leer documentación de Apple
- Probar nuevas funciones del lenguaje en side projects
- Seguir a ingenieros reconocidos de Swift e iOS
La parte importante no es solo leer actualizaciones, sino aplicarlas en código real.
125. Háblame de una feature compleja que hayas desarrollado.
Una buena respuesta de entrevista debería estructurarse así:
- Explicar brevemente la feature
- Describir los principales retos técnicos
- Explicar tus decisiones y trade-offs
- Mostrar el resultado
"Desarrollé una feature de sincronización offline-first para una app móvil. Los principales retos fueron la resolución de conflictos, la persistencia local, la lógica de retry y mantener la UI responsiva durante la sincronización. Dividí el problema en capas de networking, almacenamiento local de datos y orquestación de sincronización. También añadí políticas de retry, gestión de sincronización en background y logging. El resultado fue un mejor rendimiento percibido, mayor fiabilidad en redes inestables y menos problemas de soporte."
Las mejores respuestas son concretas y medibles.
126. ¿Cómo enfocas una code review?
Un enfoque sólido de code review se centra primero en la corrección y después en la mantenibilidad.
Prioridades típicas:
- Bugs y edge cases
- Adecuación de la arquitectura y el diseño
- Legibilidad
- Naming
- Cobertura de tests
- Rendimiento y seguridad cuando sea relevante
Una buena review debe ser clara, respetuosa y específica.
El objetivo no es solo encontrar problemas, sino también mejorar la calidad del código y compartir conocimiento.
127. ¿Cómo diseñas una arquitectura iOS escalable?
Una arquitectura escalable consiste en mantener las features fáciles de ampliar, testear y mantener a medida que la app crece.
Principios importantes:
- Separación clara de responsabilidades
- Diseño modular
- Inyección de dependencias
- Lógica de negocio testeable
- Flujo de datos predecible
Una arquitectura escalable debe ayudar a los equipos a añadir features sin reescribir constantemente el código existente.
128. ¿Qué trade-offs consideras al elegir una arquitectura?
La arquitectura siempre trata de trade-offs.
Factores comunes:
- Tamaño y experiencia del equipo
- Complejidad de la app
- Velocidad de desarrollo
- Testabilidad
- Mantenibilidad
- Flexibilidad para el crecimiento futuro
Una app simple puede no necesitar una arquitectura pesada. Una app grande suele beneficiarse de una separación más fuerte, límites más claros y una mejor gestión de dependencias.
La mejor elección es la arquitectura más simple que siga encajando bien con el problema.