Cheatsheet Swift
Referência completa da linguagem Swift — segura, rápida e moderna para iOS/macOS
Swift
Básico
Variables y constantes
let nombre = "Swift" // constante (inmutable) var edad = 10 // variable (mutable) let pi: Double = 3.14 // tipo explícito var contador: Int = 0 edad = 11 // OK (var) // nombre = "Otro" // ERROR (let) // let es preferido por seguridad: // El compilador avisa si una var nunca cambia // Permite optimizaciones agresivas // Declaración múltiple: let x = 1, y = 2, z = 3 var a, b, c: Int // sin valor inicial
let crea constantes inmutables — preferir siempre. var solo cuando el valor cambia. La inferencia de tipos elimina declaraciones redundantes. El compilador optimiza let y previene bugs de mutación accidental.
Conversiones de tipos
let x = 42
let s = String(x) // Int → String
let d = Double(x) // Int → Double
let f = Float(3.14) // Double → Float
// String → número (¡devuelve Optional!):
let n = Int("123") // Int? = 123
let inv = Int("abc") // Int? = nil
// No hay conversiones implícitas:
// let y: Double = x // ¡ERROR!
let y: Double = Double(x) // correcto
// Bool → Int no existe:
// let b = Int(true) // ¡ERROR!
let b = true ? 1 : 0 // workaroundSwift prohíbe conversiones implícitas — evita bugs silenciosos. Int("abc") devuelve nil (no crash). Siempre que conviertes String a número, el resultado es Optional. Usa Double(x) explícitamente. No hay conversión Bool→Int.
Ranges y strides
// Closed range (incluye el fin):
for i in 1...5 { print(i) } // 1 2 3 4 5
// Half-open range (excluye el fin):
for i in 0..<5 { print(i) } // 0 1 2 3 4
// Stride con paso:
for i in stride(from: 0, to: 10, by: 2) {
print(i) // 0 2 4 6 8
}
// Stride inclusivo:
for i in stride(from: 10, through: 0, by: -2) {
print(i) // 10 8 6 4 2 0
}
// Ranges como colección:
let nums = Array(1...100)
let slice = nums[5..<10]
// Verificar pertenencia:
(1...10).contains(5) // true
(1...10).contains(15) // false1...5 = cerrado (incluye 5). 0..<5 = medio-abierto (excluye 5). stride(from:to:by:) para pasos custom. through incluye el fin. Los ranges funcionan como colecciones. contains() verifica pertenencia en O(1) para ranges.
Tipos de datos
let entero: Int = 42 let decimal: Double = 3.14159 let flag: Bool = true let letra: Character = "A" let texto: String = "Swift" // Tuplas: let http: (Int, String) = (200, "OK") print(http.0) // 200 print(http.1) // "OK" // Tuplas con labels: let punto = (x: 3.0, y: 4.0) print(punto.x) // 3.0 // Tipos numéricos: // Int, Int8, Int16, Int32, Int64 // UInt, UInt8, UInt16, UInt32, UInt64 // Float (32-bit), Double (64-bit)
Swift tiene tipos estrictos — sin conversiones implícitas. Int y Double son los más usados. Character = un Unicode grapheme cluster. Las tuplas agrupan valores sin crear struct. Float = 32 bits, Double = 64 bits (preferir).
Operadores
// Aritméticos: let suma = 5 + 3 // 8 let resto = 10 % 3 // 1 let div = 10 / 3 // 3 (¡división Int!) let divD = 10.0 / 3.0 // 3.333... // Comparación: 5 == 5 // true 5 != 3 // true 5 > 3 // true 5 <= 5 // true // Lógicos: true && false // false (AND) true || false // true (OR) !true // false (NOT) // Range: 1...5 // 1,2,3,4,5 (cerrado) 1..<5 // 1,2,3,4 (medio-abierto) // Asignación compuesta: var x = 10 x += 5 // 15 x *= 2 // 30
La división entera trunca: 10 / 3 = 3. Usa Double para decimales. ... es range cerrado (incluye el fin). ..< es medio-abierto (excluye el fin). Operadores lógicos: &&, ||, !. Sin operador ternario en condiciones complejas — preferir if.
Optionals
var nombre: String? = nil // puede ser nil
nombre = "Swift"
// Unwrapping seguro (if let):
if let n = nombre {
print(n) // solo ejecuta si no-nil
}
// Optional chaining:
let tam = nombre?.count // Int? (nil si nombre es nil)
// Nil-coalescing:
let valor = nombre ?? "default"
// Force unwrap (¡evitar!):
// let x = nombre! // ¡crash si nil!
// Optional binding múltiple:
if let a = nombre, let b = nombre?.uppercased() {
print(a, b)
}Los Optionals son el mecanismo central de seguridad — fuerzan a tratar la ausencia. if let hace unwrap seguro. ?. encadena sin crash. ?? proporciona fallback. ! (force unwrap) causa crash si es nil — evitar siempre.
Type aliases y tuplas
typealias UserID = Int
typealias Callback = (String) -> Void
typealias Punto = (x: Double, y: Double)
let id: UserID = 42
let p: Punto = (x: 3.0, y: 4.0)
print(p.x) // 3.0
// Tuplas como retorno:
func dividir(_ a: Int, _ b: Int) -> (coc: Int, resto: Int) {
return (a / b, a % b)
}
let r = dividir(17, 5)
print(r.coc) // 3
print(r.resto) // 2
// Descomposición:
let (q, rest) = dividir(17, 5)typealias crea nombres semánticos (sin coste runtime). Las tuplas con labels permiten acceso por nombre. Ideales para retornos simples sin crear structs. Descomposición con let (a, b) = tupla. No abusar — para 3+ campos, preferir struct.
Strings
let saludo = "Hola"
let nombre = "Mundo"
// Interpolación (preferido):
let msg = "¡Hola, \(nombre)!"
// Concatenación:
let completa = saludo + ", " + nombre
// Multi-línea:
let multi = """
Línea 1
Línea 2
"""
// Métodos útiles:
msg.count // 13
msg.uppercased() // ¡HOLA, MUNDO!
msg.lowercased() // ¡hola, mundo!
msg.contains("Mundo") // true
msg.hasPrefix("¡Hola") // true
msg.split(separator: " ") // ["¡Hola,", "Mundo!"]La String interpolation (\()) es la forma preferida. Triple-quoted (\"\"\") para multilínea. Los strings son value types y Unicode-correct. count cuenta caracteres (no bytes). split devuelve array de Substring.
Nil-coalescing y chaining
let nombre: String? = nil
let apellido: String? = "Silva"
// Nil-coalescing (??):
let display = nombre ?? "Anónimo" // "Anónimo"
// Encadenamiento:
let resultado = nombre ?? apellido ?? "Sin nombre" // "Silva"
// Optional chaining:
let tam = nombre?.count // nil
let upper = nombre?.uppercased() // nil
// Chaining + coalescing:
let len = nombre?.count ?? 0 // 0
// Multi-nivel:
struct User { var address: Address? }
struct Address { var city: String? }
let city = user?.address?.city ?? "N/A"?? proporciona default cuando el optional es nil. El encadenamiento crea fallbacks en cascada. ?. encadena llamadas — devuelve nil si cualquier nivel es nil. Combinar ?. + ?? es el patrón idiomático. Sustituye if-let verboso para casos simples.
Controlo de Fluxo
If / Else
let temp = 25
if temp > 30 {
print("Caliente")
} else if temp > 20 {
print("Agradable")
} else {
print("Frío")
}
// Llaves OBLIGATORIAS (incluso 1 línea)
// Paréntesis opcionales en la condición
// La condición DEBE ser Bool (no acepta Int)
// If como expresión (Swift 5.9+):
let msg = if temp > 25 { "caliente" } else { "frío" }
// If-let (optional binding):
if let nombre = optionalNombre {
print(nombre) // nombre unwrapped aquí
}Las llaves {} son obligatorias — previene bugs de indentación. La condición debe ser Bool (no acepta enteros como C). if let hace unwrap de optionals. Swift 5.9+ permite if como expresión. Los paréntesis en la condición son opcionales.
Guard y early exit
func procesar(nombre: String?, edad: Int?) {
guard let n = nombre else {
print("Nombre obligatorio")
return // DEBE salir del scope
}
guard let i = edad, i >= 18 else {
print("Edad inválida")
return
}
// Aquí n e i están unwrapped:
print("\(n) tiene \(i) años")
}
// Ventaja sobre if-let:
// - Happy path sin anidamiento
// - Variable disponible en todo el scope
// - Condición de fallo clara arriba
// guard con throw:
func validar(_ x: Int) throws {
guard x > 0 else { throw ValidationError.negativo }
}guard verifica condiciones y sale pronto si fallan. El else DEBE salir del scope (return, throw, break). La variable unwrapped queda disponible en el resto de la función. Preferido sobre if-let cuando necesitas el valor en todo el scope.
Labeled statements
// Labels en bucles:
outer: for i in 1...5 {
inner: for j in 1...5 {
if i * j > 12 {
break outer // ¡sale del bucle externo!
}
if j == 3 {
continue inner // siguiente j
}
print("\(i)×\(j) = \(i*j)")
}
}
// Label en switch dentro de bucle:
loop: while true {
switch estado {
case .salir:
break loop // ¡sale del while!
case .continuar:
continue loop
default:
break // solo sale del switch
}
}Los labels (nombre:) identifican bucles para break/continue específicos. break outer sale del bucle externo. Sin label, break dentro de switch solo sale del switch. Esencial para bucles anidados y máquinas de estados.
Switch
let dia = 3
switch dia {
case 1:
print("Lunes")
case 2, 3:
print("Martes o Miércoles")
case 4...6:
print("Mitad de semana")
default:
print("Fin de semana")
}
// DEBE ser exhaustivo (o tener default)
// NO hace fallthrough automático
// break es implícito (no necesario)
// fallthrough explícito (raro):
switch dia {
case 1:
print("Inicio")
fallthrough
case 2:
print("También")
default: break
}switch debe ser exhaustivo — cubrir todos los casos o tener default. No hace fallthrough automático (evita bugs de C). Soporta ranges (4...6), múltiples valores (2, 3) y patrones complejos. break es implícito.
Pattern matching
let punto = (x: 3, y: -2)
switch punto {
case (0, 0):
print("Origen")
case (_, 0):
print("Eje X")
case (0, _):
print("Eje Y")
case (-2...2, -2...2):
print("Cerca del origen")
default:
print("Punto (\(punto.x), \(punto.y))")
}
// if-case:
if case (let x, 0) = punto {
print("x = \(x)")
}
// for-case:
let datos: [Any] = [1, "dos", 3.0, "cuatro"]
for case let s as String in datos {
print(s) // "dos", "cuatro"
}El pattern matching descompone tuplas, verifica ranges y usa wildcards (_). if case extrae patrones fuera de switch. for case let x as Type filtra por tipo. Poderoso para enums con associated values, parsing y validación de estados.
For-In
// Range:
for i in 1...5 {
print(i) // 1 2 3 4 5
}
// Ignorar valor:
for _ in 1...3 {
print("Hola") // 3x
}
// Colección con índice:
let frutas = ["manzana", "banana", "cereza"]
for (i, fruta) in frutas.enumerated() {
print("\(i): \(fruta)")
}
// Diccionario:
let caps = ["PT": "Lisbon", "BR": "Brasilia"]
for (país, cap) in caps {
print("\(país) → \(cap)")
}
// Filtro inline (where):
for i in 1...20 where i % 3 == 0 {
print(i) // 3 6 9 12 15 18
}for-in itera ranges, arrays, dicts, sets. _ ignora el valor. enumerated() da índice + valor. Los diccionarios devuelven tuplas (key, value). where filtra inline. No existe for (i=0; i<n; i++) — usar ranges.
Ternario y expresiones
let edad = 20
let status = edad >= 18 ? "adulto" : "menor"
// Switch como expresión (Swift 5.9+):
let dia = 3
let nombre = switch dia {
case 1: "Lunes"
case 2: "Martes"
case 3: "Miércoles"
default: "Otro"
}
// If como expresión (Swift 5.9+):
let msg = if edad >= 18 {
"Mayor de edad"
} else {
"Menor de edad"
}
// Encadenar ternarios (¡evitar!):
// let x = a ? b : c ? d : e // ilegibleOperador ternario (?:) para condiciones simples. Swift 5.9+ permite switch e if como expresiones (asignar resultado). Evitar encadenar ternarios — ilegible. Preferir switch expresión para 3+ casos.
While y Repeat-While
// While (verifica antes):
var n = 5
while n > 0 {
print(n)
n -= 1
}
// Repeat-while (ejecuta al menos 1 vez):
var input: String
repeat {
input = readLine() ?? ""
print("Recibido: \(input)")
} while input != "salir"
// Equivalente al do-while de C/Java
// Útil para:
// - Validación de input
// - Menús interactivos
// - Retry con condición
// Cuidado: ¡garantizar que termina!
// var x = 0
// while x < 10 { } // ¡bucle infinito!while verifica antes (puede no ejecutarse nunca). repeat-while ejecuta al menos una vez (equivalente al do-while). Ideal para validación de input y menús. Garantizar que la condición eventualmente es falsa — evitar bucles infinitos.
Defer
func leerArchivo() throws -> String {
let file = try openFile("datos.txt")
defer {
file.close() // SIEMPRE ejecuta
print("Archivo cerrado")
}
// Incluso con throw o return temprano:
guard file.isValid else {
throw FileError.invalido
// ¡defer ejecuta aquí!
}
return try file.read()
// ¡defer ejecuta aquí también!
}
// Múltiples defer (orden inverso):
defer { print("1º") }
defer { print("2º") }
// Output: "2º", "1º"defer ejecuta código al salir del scope — incluso con return, throw o break. Ideal para cleanup: cerrar archivos, liberar recursos, unlock. Múltiples defer ejecutan en orden inverso (LIFO). Equivale al finally de Java/C#.
Funções e Closures
Declaración de funciones
func suma(_ a: Int, _ b: Int) -> Int {
return a + b
}
// Con argument labels:
func greet(to nombre: String, from remitente: String) -> String {
return "Hola \(nombre), de \(remitente)"
}
greet(to: "Ana", from: "Ray")
// Sin retorno:
func log(_ msg: String) {
print("[LOG] \(msg)")
}
// Retorno implícito (Swift 5.1+):
func doble(_ x: Int) -> Int { x * 2 }
// Múltiples retornos (tupla):
func stats(_ nums: [Int]) -> (min: Int, max: Int, avg: Double) {
(nums.min()!, nums.max()!, Double(nums.reduce(0,+)) / Double(nums.count))
}func declara funciones. Los labels externos hacen las llamadas auto-documentadas. _ suprime el label. Retorno implícito en funciones de una expresión. Tuplas para múltiples retornos. -> Void se omite cuando no hay retorno.
Funciones como tipos
// Tipo de función:
var operacion: (Int, Int) -> Int
operacion = { $0 + $1 }
print(operacion(3, 4)) // 7
operacion = { $0 * $1 }
print(operacion(3, 4)) // 12
// Función que devuelve función:
func multiplicador(_ factor: Int) -> (Int) -> Int {
return { $0 * factor }
}
let triple = multiplicador(3)
triple(5) // 15
// Función como parámetro:
func aplicar(_ f: (Int) -> Int, _ x: Int) -> Int {
f(x)
}
aplicar({ $0 * 2 }, 5) // 10Las funciones son first-class citizens — almacenables, pasables, retornables. El tipo (Int, Int) -> Int describe la firma. Permite estrategias dinámicas y composición. Base para callbacks, delegates y programación funcional.
Funciones genéricas
func intercambiar<T>(_ a: inout T, _ b: inout T) {
(a, b) = (b, a)
}
var x = 1, y = 2
intercambiar(&x, &y) // x=2, y=1
// Con constraints:
func máximo<T: Comparable>(_ a: T, _ b: T) -> T {
a > b ? a : b
}
máximo(3, 7) // 7
máximo("a", "z") // "z"
// Where clause:
func primerPar<T: Collection>(_ c: T) -> T.Element?
where T.Element: BinaryInteger {
c.first { $0 % 2 == 0 }
}
// Múltiples tipos:
func zip<A, B>(_ a: [A], _ b: [B]) -> [(A, B)] {
Array(Swift.zip(a, b))
}Las funciones genéricas funcionan con cualquier tipo manteniendo type safety. <T> declara el tipo. Constraints (T: Comparable) restringen. Cláusula where para condiciones complejas. El compilador especializa para cada tipo concreto (zero-cost).
Parámetros especiales
// Default values:
func conectar(host: String, puerto: Int = 3306) {
print("\(host):\(puerto)")
}
conectar(host: "localhost") // puerto = 3306
conectar(host: "db", puerto: 5432) // puerto = 5432
// Variadic (...):
func media(_ nums: Double...) -> Double {
nums.reduce(0, +) / Double(nums.count)
}
media(8, 9, 10) // 9.0
// Inout (paso por referencia):
func intercambiar(_ a: inout Int, _ b: inout Int) {
(a, b) = (b, a)
}
var x = 1, y = 2
intercambiar(&x, &y) // x=2, y=1Los default values eliminan sobrecargas. ... (variadic) acepta N argumentos. inout pasa por referencia (modifica el original) — requiere & en la llamada. Orden: normales → default → variadic. Un variadic por función.
Escaping y Autoclosure
// @escaping: el closure sobrevive a la función
var callbacks: [() -> Void] = []
func registrar(_ cb: @escaping () -> Void) {
callbacks.append(cb) // almacenado para después
}
// @autoclosure: pospone la evaluación
func log(nivel: String, msg: @autoclosure () -> String) {
if nivel == "DEBUG" {
print(msg()) // solo evalúa si es necesario
}
}
log(nivel: "INFO", msg: "Datos: \(datosCaros())")
// datosCaros() NO se ejecuta si nivel != DEBUG
// Sin @escaping: el closure se llama DENTRO de la función
func ejecutar(_ cb: () -> Void) {
cb() // no necesita @escaping
}@escaping indica que el closure será llamado después de que la función retorne (callbacks, async). @autoclosure envuelve una expresión en un closure sin {} — permite evaluación perezosa. Sin @escaping, el closure se llama dentro de la función.
Closures
// Sintaxis completa:
let suma = { (a: Int, b: Int) -> Int in
return a + b
}
// Inferencia de tipos:
let doble: (Int) -> Int = { $0 * 2 }
// Trailing closure:
let nums = [3, 1, 4, 1, 5]
let ordenado = nums.sorted { $0 < $1 }
// Shorthand extremo:
let asc = nums.sorted(by: <)
// Closure que captura variables:
var total = 0
let acumular = { (x: Int) in total += x }
acumular(5)
acumular(3)
print(total) // 8Las closures son bloques auto-contenidos (lambdas). $0, $1 son shorthand para parámetros. La trailing closure simplifica cuando es el último argumento. sorted(by: <) es el caso más conciso. Las closures capturan variables del scope envolvente por referencia.
Throwing functions
enum ParseError: Error {
case vacio
case invalido(linea: Int)
}
func parse(_ input: String) throws -> [Int] {
guard !input.isEmpty else { throw ParseError.vacio }
return input.split(separator: ",").enumerated().map { i, s in
guard let n = Int(s.trimmingCharacters(in: .whitespaces)) else {
throw ParseError.invalido(linea: i + 1)
}
return n
}
}
// Llamar:
do {
let nums = try parse("1, 2, 3")
} catch ParseError.invalido(let linea) {
print("Error en la línea \(linea)")
}
// try? (optional) y try! (force):
let nums = try? parse("1,2") // [Int]?throws en la firma indica que puede lanzar errores. throw lanza. try llama. do-catch captura casos específicos. try? convierte en optional (nil si hay error). try! crashea si hay error (evitar). Enums conformando Error definen dominios.
Higher-order functions
let nums = [1, 2, 3, 4, 5]
// map: transforma cada elemento
let dobles = nums.map { $0 * 2 } // [2,4,6,8,10]
// filter: selecciona por condición
let pares = nums.filter { $0 % 2 == 0 } // [2,4]
// reduce: acumula en un valor
let suma = nums.reduce(0) { $0 + $1 } // 15
let suma2 = nums.reduce(0, +) // 15
// compactMap: transforma + elimina nils
let strs = ["1", "a", "3"].compactMap { Int($0) }
// [1, 3]
// forEach: itera sin retornar
nums.forEach { print($0) }map/filter/reduce son la base funcional. compactMap descarta nils. forEach para side effects. No mutan la colección original (devuelven una nueva). Encadenables para pipelines declarativos. Preferir sobre bucles manuales.
Async functions
// Función asíncrona:
func fetchDatos() async throws -> [String] {
let (data, _) = try await URLSession.shared
.data(from: url)
return try JSONDecoder().decode([String].self, from: data)
}
// Llamar con Task:
Task {
do {
let datos = try await fetchDatos()
print(datos)
} catch {
print("Error: \(error)")
}
}
// Múltiples llamadas en paralelo:
async let a = fetchA()
async let b = fetchB()
let resultados = try await [a, b]
// await suspende sin bloquear el hiloasync marca funciones asíncronas. await suspende sin bloquear el hilo. Task {} crea un contexto de ejecución. async let ejecuta en paralelo. Sustituye completion handlers con código secuencial legible. Requiere Swift 5.5+.
Coleções
Arrays
var nums = [1, 2, 3, 4, 5] nums.append(6) nums.insert(0, at: 0) nums.remove(at: 0) nums[0] = 10 print(nums.count) // 5 print(nums.isEmpty) // false print(nums.first ?? 0) // 10 print(nums.contains(3)) // true // Operaciones: nums += [7, 8] // concatenar let slice = nums[1...3] // ArraySlice let reversed = nums.reversed() // Tipados: let nombres: [String] = ["Ana", "Ray"] let matriz: [[Int]] = [[1,2], [3,4]]
Los arrays son value types con copy-on-write. Tipados ([Int] o Array<Int>). append/insert/remove para mutación. first/last devuelven optionals. += concatena. Los slices son views (no copian).
Ordenación y búsqueda
let nums = [5, 2, 8, 1, 9]
let asc = nums.sorted() // [1,2,5,8,9]
let desc = nums.sorted(by: >) // [9,8,5,2,1]
// Comparador custom:
struct Persona { let nombre: String; let edad: Int }
let personas = [Persona(nombre: "Ana", edad: 25),
Persona(nombre: "Ray", edad: 30)]
let porEdad = personas.sorted { $0.edad < $1.edad }
// Búsqueda:
let primero = nums.first { $0 > 4 } // 5
let índice = nums.firstIndex(of: 8) // Optional(2)
let todos = nums.filter { $0 > 3 } // [5,8,9]
// contains con condición:
nums.contains { $0 > 8 } // truesorted() devuelve un array nuevo (no muta). sorted(by:) acepta closure custom. first(where:) encuentra el 1º elemento. firstIndex(of:) devuelve Optional. contains(where:) verifica existencia. Todo inmutable por default.
Colecciones custom (Sequence)
// Conformar Sequence:
struct Countdown: Sequence {
let start: Int
func makeIterator() -> CountdownIterator {
CountdownIterator(current: start)
}
}
struct CountdownIterator: IteratorProtocol {
var current: Int
mutating func next() -> Int? {
guard current > 0 else { return nil }
defer { current -= 1 }
return current
}
}
// Uso:
for n in Countdown(start: 5) {
print(n) // 5 4 3 2 1
}
// Gana map, filter, reduce, etc.:
let pares = Countdown(start: 10).filter { $0 % 2 == 0 }Conformar Sequence + IteratorProtocol crea colecciones custom. next() devuelve nil al terminar. Gana todas las operaciones funcionales gratis (map, filter, reduce). Ideal para generadores, streams y wrappers.
Dictionaries
var capitales = ["PT": "Lisbon", "BR": "Brasília"]
capitales["ES"] = "Madrid" // añadir
capitales["PT"] = "Porto" // actualizar
let eliminado = capitales.removeValue(forKey: "BR")
// Acceso seguro (devuelve Optional):
if let cap = capitales["PT"] {
print(cap) // "Porto"
}
// Default value:
let valor = capitales["XX", default: "N/A"]
// Iteración:
for (país, cap) in capitales {
print("\(país) → \(cap)")
}
// Claves y valores:
let paises = Array(capitales.keys)
let ciudades = Array(capitales.values)Los dictionaries mapean claves únicas a valores. El acceso por clave devuelve Optional. default: evita el unwrap. removeValue(forKey:) elimina y devuelve. La iteración devuelve tuplas (key, value). Las claves deben ser Hashable.
flatMap y compactMap
// flatMap: aplana colecciones anidadas
let listas = [[1,2], [3,4], [5]]
let plana = listas.flatMap { $0 } // [1,2,3,4,5]
// compactMap: elimina nils
let strings = ["1", "abc", "3", "4x"]
let nums = strings.compactMap { Int($0) } // [1, 3]
// Combinados:
let resultado = listas
.flatMap { $0 }
.filter { $0 > 2 } // [3,4,5]
// Caso real: parsing de array JSON
let ids = respuestas.compactMap { $0["id"] as? Int }
// flatMap con optional:
let opt: [Int?] = [1, nil, 3, nil, 5]
let validos = opt.compactMap { $0 } // [1,3,5]flatMap transforma y aplana colecciones anidadas. compactMap es un map que descarta nils — esencial para parsing. Juntos eliminan bucles anidados y verificaciones manuales. compactMap { $0 } elimina nils de un array de optionals.
Sets
let primos: Set = [2, 3, 5, 7, 11]
let pares: Set = [2, 4, 6, 8, 10]
// Operaciones de conjuntos:
let union = primos.union(pares)
let interseccion = primos.intersection(pares) // {2}
let diferencia = primos.subtracting(pares) // {3,5,7,11}
let simetrica = primos.symmetricDifference(pares)
// Verificaciones:
primos.contains(5) // true
primos.isSubset(of: union) // true
primos.isDisjoint(with: pares) // false
// Eliminar duplicados:
let nums = [1, 2, 2, 3, 3, 3]
let unicos = Array(Set(nums)) // [1, 2, 3]
// Inserción/eliminación:
var s: Set<Int> = [1, 2]
s.insert(3)
s.remove(2)Los sets almacenan elementos únicos sin orden. Ideales para verificar pertenencia (O(1)) y eliminar duplicados. Operaciones: union, intersection, subtracting, symmetricDifference. Requieren Hashable. Sin orden garantizado.
Lazy y performance
let nums = Array(1...1_000_000)
// Eager: crea arrays intermedios
let r1 = nums.filter { $0 % 2 == 0 }
.map { $0 * 2 }
.prefix(5)
// Lazy: procesa solo lo necesario
let r2 = nums.lazy
.filter { $0 % 2 == 0 }
.map { $0 * 2 }
.prefix(5)
let resultado = Array(r2) // [2,4,6,8,10]
// Sin lazy: procesa 1M elementos
// Con lazy: procesa ~10 elementos
// Cuándo usar lazy:
// - Colecciones grandes
// - Solo necesitas prefix/suffix
// - Pipeline con filter + maplazy pospone la computación hasta que sea necesaria — procesa elemento a elemento. Crucial para colecciones grandes con prefix. Sin lazy: crea arrays intermedios (memoria). Con lazy: O(k) en vez de O(n). Convertir con Array() al final.
Operaciones funcionales
let nums = [1, 2, 3, 4, 5, 6]
let dobles = nums.map { $0 * 2 }
let pares = nums.filter { $0 % 2 == 0 }
let suma = nums.reduce(0, +) // 21
// joined:
let texto = nums.map(String.init)
.joined(separator: ", ")
// "1, 2, 3, 4, 5, 6"
// Encadenamiento (pipeline):
let resultado = nums
.filter { $0 > 2 }
.map { $0 * 10 }
.reduce(0, +)
// 30+40+50+60 = 180
// sorted + prefix:
let top3 = nums.sorted(by: >).prefix(3) // [6,5,4]map/filter/reduce para transformaciones declarativas. El encadenamiento crea pipelines legibles. joined convierte a string. sorted(by:) + prefix para top-N. Devuelven arrays nuevos (no mutan). Preferir sobre bucles manuales.
Dictionary operations
var scores = ["Ana": 85, "Ray": 92, "Mia": 78]
// Actualizar con default:
scores["Ana", default: 0] += 5 // 90
// mapValues (mantiene claves):
let bonus = scores.mapValues { $0 + 10 }
// filter:
let aprobados = scores.filter { $0.value >= 80 }
// merge:
let extras = ["Zé": 70]
scores.merge(extras) { old, new in new }
// Agrupar (grouping):
let nums = [1, 2, 3, 4, 5, 6]
let grupos = Dictionary(grouping: nums) { $0 % 2 == 0 ? "par" : "impar" }
// ["par": [2,4,6], "impar": [1,3,5]]
// Compactar:
let keys = ["a", "b", "c"]
let vals = [1, 2, 3]
let dict = Dictionary(uniqueKeysWithValues: zip(keys, vals))default: para actualizar sin verificar existencia. mapValues transforma valores manteniendo claves. merge combina dicts con closure de conflicto. Dictionary(grouping:by:) agrupa por criterio. zip + uniqueKeysWithValues crea dict de arrays.
Classes e Structs
Structs
struct Punto {
var x: Double
var y: Double
func distancia(a otro: Punto) -> Double {
let dx = x - otro.x
let dy = y - otro.y
return (dx*dx + dy*dy).squareRoot()
}
}
// Memberwise init automático:
var p = Punto(x: 3, y: 4)
// Value type (copia independiente):
var q = p
q.x = 10
print(p.x) // 3 (¡inalterado!)
// La mutación requiere var:
p.x = 5 // OK (var)struct es value type — cada copia es independiente. Recibe memberwise init gratis. Preferida para modelos de datos. La mutación requiere var. Copy-on-write para eficiencia. Es el tipo más usado en Swift moderno.
Extensions
extension Int {
var esPar: Bool { self % 2 == 0 }
var alCuadrado: Int { self * self }
func repetido(_ veces: Int) {
for _ in 0..<veces { print(self) }
}
}
4.esPar // true
5.alCuadrado // 25
3.repetido(2) // imprime 3 dos veces
// Extensión de String:
extension String {
var capitalizado: String {
prefix(1).uppercased() + dropFirst()
}
}
"swift".capitalizado // "Swift"extension añade funcionalidad a tipos existentes (incluidos los del sistema). Puede añadir propiedades computadas, métodos y conformancia a protocolos. No puede añadir stored properties. Ideal para utilidades y DSLs.
Mutating y static
struct Contador {
var valor = 0
// mutating: modifica self en struct
mutating func incrementar() {
valor += 1
}
mutating func reset() {
self = Contador() // sustituir self
}
}
var c = Contador()
c.incrementar() // OK (var)
// Static (tipo, no instancia):
struct Math {
static let pi = 3.14159
static func doble(_ x: Int) -> Int { x * 2 }
}
Math.pi // 3.14159
Math.doble(5) // 10
// En clases: class func (overridable)mutating permite que métodos de struct modifiquen propiedades. Sin mutating, los métodos de struct son read-only. static pertenece al tipo (no a la instancia). class func en clases es overridable. self = ... sustituye toda la struct.
Clases y herencia
class Animal {
let nombre: String
init(nombre: String) { self.nombre = nombre }
func sonido() -> String { "..." }
}
class Perro: Animal {
override func sonido() -> String { "¡Guau!" }
}
let c = Perro(nombre: "Rex")
print(c.sonido()) // "¡Guau!"
// Reference type (compartido):
let a = c
a.nombre // "Rex" — ¡mismo objeto!
// Deinitializer:
class Archivo {
deinit { print("Cerrado") }
}
// Verificar tipo:
c is Animal // true
c as? Animal // Animal?class es reference type — múltiples variables apuntan al mismo objeto. Soporta herencia (una base), override y deinit. is verifica el tipo, as? hace cast. Usar cuando se necesita identidad compartida o herencia.
Protocol extensions
protocol Numeric {
var valor: Double { get }
}
// Implementación default:
extension Numeric {
var alCuadrado: Double { valor * valor }
func descripcion() -> String {
"Valor: \(valor), Cuadrado: \(alCuadrado)"
}
}
struct Moneda: Numeric {
let valor: Double
}
Moneda(valor: 5).alCuadrado // 25.0
// Retroactive conformance:
extension Int: Numeric {
var valor: Double { Double(self) }
}
42.descripcion() // "Valor: 42.0, Cuadrado: 1764.0"Las protocol extensions dan implementación default — cualquier tipo conforme gana métodos gratis. Permite retroactive conformance (conformar tipos existentes). Base del POP — alternativa a la herencia clásica. Composición > herencia.
Protocolos
protocol Descriptible {
var descripcion: String { get }
func resumir() -> String
}
struct Libro: Descriptible {
let título: String
var descripcion: String { "Libro: \(título)" }
func resumir() -> String { título }
}
// Protocol como tipo:
let items: [any Descriptible] = [Libro(título: "Swift")]
// Conformancia múltiple:
struct Producto: Descriptible, Codable, Hashable {
// implementar todos
}
// Protocol composition:
func mostrar(_ item: Descriptible & Codable) { }protocol define contratos sin implementación. Cualquier tipo puede conformar. any Protocol (existential) para colecciones heterogéneas. La composición con & exige múltiples protocolos. Base del Protocol-Oriented Programming.
Properties y observers
struct Círculo {
var radio: Double
// Computed property:
var area: Double { .pi * radio * radio }
// Getter/Setter:
var diametro: Double {
get { radio * 2 }
set { radio = newValue / 2 }
}
}
// Property observers:
class Config {
var volume: Int = 50 {
willSet { print("De \(volume) a \(newValue)") }
didSet { print("Cambió de \(oldValue)") }
}
}
// Lazy (calculada en el 1º acceso):
struct DB {
lazy var conexion = Conexion() // solo se crea al usarse
}Las computed properties calculan on-demand. willSet/didSet observan cambios. lazy pospone la inicialización hasta el 1º acceso. newValue y oldValue son implícitos. Ideal para validación, logging y caché.
Enums con associated values
enum Resultado {
case exito(datos: String)
case error(código: Int, msg: String)
case cargando
}
let r = Resultado.exito(datos: "JSON")
switch r {
case .exito(let datos):
print("Datos: \(datos)")
case .error(let cod, let msg):
print("Error \(cod): \(msg)")
case .cargando:
print("Espere...")
}
// Raw values:
enum HTTPMethod: String {
case get = "GET"
case post = "POST"
}
HTTPMethod.get.rawValue // "GET"Los enums en Swift son poderosos — cada caso puede tener associated values de tipos diferentes. Sustituyen jerarquías de clases para estados. Pattern matching en switch extrae valores. Raw values para enums simples (String, Int).
Subscripts
struct Matrix {
let rows: Int, cols: Int
var data: [Double]
subscript(row: Int, col: Int) -> Double {
get { data[row * cols + col] }
set { data[row * cols + col] = newValue }
}
}
var m = Matrix(rows: 2, cols: 2, data: [1,2,3,4])
m[0, 1] // 2.0
m[1, 0] = 9 // set
// Subscript con tipo diferente:
extension String {
subscript(i: Int) -> Character {
self[index(startIndex, offsetBy: i)]
}
}
"Swift"[0] // "S"subscript permite acceso por índice como arrays. Puede tener múltiples parámetros y tipos. Getter y setter opcionales. Ideal para matrices, grids y colecciones custom. Sintaxis natural: m[0, 1] en vez de m.get(0, 1).
Avançado
Generics avanzados
struct Stack<Element> {
private var items: [Element] = []
mutating func push(_ item: Element) {
items.append(item)
}
mutating func pop() -> Element? {
items.popLast()
}
}
var stack = Stack<Int>()
stack.push(1)
stack.push(2)
stack.pop() // 2
// Associated types:
protocol Container {
associatedtype Item
mutating func add(_ item: Item)
var count: Int { get }
}
// Opaque types (some):
func makeShape() -> some Shape {
Circle(radius: 5) // tipo concreto oculto
}Los generics permiten código reutilizable con type safety. associatedtype en protocolos define tipos abstractos. some Protocol (opaque type) oculta el tipo concreto pero mantiene la performance. any Protocol (existential) permite heterogeneidad.
Property wrappers
@propertyWrapper
struct Clamped {
private var value: Int
let range: ClosedRange<Int>
var wrappedValue: Int {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
init(wrappedValue: Int, range: ClosedRange<Int>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
struct Config {
@Clamped(range: 0...100) var volume = 50
}
var c = Config()
c.volume = 150
print(c.volume) // 100 (¡clamped!)Los property wrappers encapsulan lógica de getter/setter reutilizable. @Clamped restringe valores automáticamente. wrappedValue es la propiedad. Usados en SwiftUI: @State, @Binding, @ObservedObject. Eliminan validación repetitiva.
Sendable y data races
// Sendable: tipo seguro para concurrencia
struct Punto: Sendable {
let x, y: Double
}
// Structs con let son Sendable automáticamente
// Las clases necesitan @unchecked o actor
// @Sendable closure:
func ejecutar(_ work: @Sendable () async -> Void) {
Task { await work() }
}
// Non-Sendable (¡cuidado!):
class Cache { // NO es Sendable
var data: [String: Any] = [:]
}
// Solución: actor
actor SafeCache {
var data: [String: Any] = [:]
}
// Swift 6: strict concurrency checking
// ¡Data races = error de compilación!Sendable marca tipos seguros para pasar entre tasks. Los structs inmutables son Sendable automáticamente. Las clases mutables NO lo son — usar actor. @Sendable en closures. Swift 6 convierte data races en errores de compilación (strict concurrency).
Error handling avanzado
enum NetworkError: Error, LocalizedError {
case sinConexion
case timeout(segundos: Int)
case servidor(code: Int)
var errorDescription: String? {
switch self {
case .sinConexion: "Sin conexión"
case .timeout(let s): "Timeout: \(s)s"
case .servidor(let c): "Error \(c)"
}
}
}
// try? y try!:
let datos = try? fetch() // Optional
let datos2 = try! fetch() // ¡crash si hay error!
// rethrows:
func process(_ f: () throws -> Int) rethrows -> Int {
try f()
}
// Never (no retorna):
func fatalError(_ msg: String) -> Never {
print(msg)
exit(1)
}LocalizedError añade mensajes legibles. try? convierte en optional. try! crashea (evitar). rethrows solo lanza si el argumento lanza. Never indica que no retorna. Enums con associated values para errores ricos.
Result builders
@resultBuilder
struct HTMLBuilder {
static func buildBlock(_ parts: String...) -> String {
parts.joined(separator: "\n")
}
static func buildOptional(_ part: String?) -> String {
part ?? ""
}
}
func html(@HTMLBuilder content: () -> String) -> String {
"<html>\n\(content())\n</html>"
}
let showFooter = true
let page = html {
"<h1>Título</h1>"
"<p>Contenido</p>"
if showFooter {
"<footer>Fin</footer>"
}
}Los result builders permiten sintaxis declarativa (DSL) — la base de SwiftUI. buildBlock combina expresiones. buildOptional soporta if. El compilador transforma el bloque en llamadas al builder. Usado en SwiftUI, Vapor (HTML) y DSLs custom.
Async/Await y TaskGroup
// Concurrencia estructurada:
func fetchAll(urls: [URL]) async throws -> [Data] {
try await withThrowingTaskGroup(of: Data.self) { group in
for url in urls {
group.addTask {
try await URLSession.shared.data(from: url).0
}
}
return try await group.reduce(into: []) { $0.append($1) }
}
}
// Task con prioridad:
Task(priority: .high) {
let datos = try await fetchDatos()
}
// Sleep:
try await Task.sleep(for: .seconds(2))
// Cancelación:
let task = Task { try await trabajo() }
task.cancel()TaskGroup ejecuta tareas en paralelo con tratamiento unificado. withThrowingTaskGroup para funciones que lanzan. Task(priority:) define la prioridad. Task.sleep suspende. task.cancel() cancela. Concurrencia estructurada = sin callbacks.
Memory management (ARC)
// ARC: Automatic Reference Counting
class Node {
let value: Int
var next: Node? // strong ref
weak var prev: Node? // weak ref (¡no cuenta!)
init(_ v: Int) { value = v }
deinit { print("Node \(value) liberado") }
}
var a: Node? = Node(1)
var b: Node? = Node(2)
a?.next = b // strong: b retenido
b?.prev = a // weak: no retiene a
a = nil // Node 1 liberado (sin strong refs)
b = nil // Node 2 liberado
// Closures: capture list
var handler: (() -> Void)?
let obj = Objeto()
handler = { [weak obj] in
guard let obj else { return }
obj.hacer()
}ARC cuenta referencias strong. Cuando llega a 0, libera. weak no cuenta (evita ciclos). unowned como weak pero crashea si es nil. Los closures capturan strong por default — usar [weak self] para evitar retain cycles. deinit para cleanup.
Actors
actor BankAccount {
var balance: Double = 0
func deposit(_ amount: Double) {
balance += amount
}
func withdraw(_ amount: Double) throws -> Double {
guard balance >= amount else {
throw BankError.insuficiente
}
balance -= amount
return balance
}
}
// El acceso requiere await:
let account = BankAccount()
await account.deposit(100)
let nuevo = try await account.withdraw(50)
// MainActor (hilo de UI):
@MainActor
class ViewModel: ObservableObject {
@Published var items: [String] = []
}actor protege el estado mutable de data races automáticamente. Solo una task accede al estado a la vez. El acceso externo requiere await. @MainActor garantiza ejecución en el hilo principal (UI). Sustituye locks manuales con seguridad del compilador.
Macros (Swift 5.9+)
// Los macros generan código en compilación:
// Attached macro:
@Observable
class ViewModel {
var items: [String] = []
var selected: String?
}
// Genera: observation tracking automático
// Freestanding macro:
let url = #URL("https://swift.org")
// ¡Valida la URL en compilación!
// Macro #Preview (SwiftUI):
#Preview {
ContentView()
}
// Macro custom (sintaxis):
// @attached(member)
// public macro Model() = #externalMacro(...)
// Los macros se expanden en compilación
// (no en runtime) — zero overheadLos macros (Swift 5.9+) generan código en compilación. @Observable sustituye ObservableObject. #URL valida en compilación. #Preview para SwiftUI. Attached (@) vs freestanding (#). Zero overhead — expandidos por el compilador.
Instalação e Setup
Instalar Xcode (macOS)
// 1. Instalar Xcode desde la App Store (gratis) // 2. Instalar Command Line Tools: xcode-select --install // 3. Verificar instalación: swift --version // Swift version 6.0 (swiftlang-6.0.0) // 4. Aceptar licencia: sudo xcodebuild -license accept // Xcode incluye: // - Compilador Swift (swiftc) // - REPL (swift) // - Package Manager (swift package) // - Simuladores iOS/watchOS/tvOS // - Interface Builder y SwiftUI Preview
Xcode es el IDE oficial (solo macOS). xcode-select --install instala las CLI tools. swift --version confirma la instalación. Incluye compilador, REPL, simuladores y Swift Package Manager. Necesario para desarrollo iOS/macOS.
Dependencias (SPM)
// Package.swift
// swift-tools-version: 6.0
import PackageDescription
let package = Package(
name: "MiApp",
dependencies: [
.package(url: "https://github.com/Alamofire/Alamofire.git",
from: "5.9.0"),
.package(url: "https://github.com/apple/swift-log.git",
from: "1.5.0"),
],
targets: [
.executableTarget(
name: "MiApp",
dependencies: ["Alamofire",
.product(name: "Logging",
package: "swift-log")]
)
]
)Package.swift declara dependencias con .package(url:from:). from: usa semantic versioning. .product(name:package:) cuando el nombre del producto difiere del paquete. swift package resolve las descarga. Las dependencias quedan en .build/.
Herramientas esenciales
// sourcekit-lsp: language server (editores)
// Integrado en Xcode, VS Code, Neovim
// swift-format: formateador oficial
swift format --in-place Sources/
// swift-docc: documentación
swift package generate-documentation
// Instruments: profiling (Xcode)
// Product → Profile → Time Profiler
// lldb: debugger
$ lldb ./app
(lldb) breakpoint set --line 10
(lldb) run
(lldb) print variable
(lldb) po objeto // object description
// swift-testing (Swift 6):
// import Testing
// @Test func test() { #expect(2 + 2 == 4) }sourcekit-lsp da autocompletado en editores. swift-format formatea código. Instruments hace profiling (memoria, CPU). lldb es el debugger (po imprime objetos). swift-testing es el framework moderno de tests (sustituye XCTest).
Swift en Linux/Windows
// Linux (Ubuntu/Debian): // 1. Descargar en swift.org/download // 2. Extraer y añadir al PATH: export PATH=/usr/local/swift/usr/bin:$PATH // 3. Verificar: swift --version // Windows: // 1. Instalar Visual Studio 2022 (C++ workload) // 2. Descargar el installer en swift.org // 3. Instalar y usar vía Developer Command Prompt // Docker: docker run --rm swift:6.0 swift --version // Nota: SwiftUI/UIKit solo disponibles en macOS // En Linux: Foundation, networking, server-side
Swift es open-source y funciona en Linux y Windows. Descarga en swift.org. Docker tiene imágenes oficiales. En Linux/Windows: sin SwiftUI/UIKit (solo server-side y CLI). Ideal para backend con Vapor o Hummingbird.
Hello World y estructura
// main.swift (o top-level en scripts)
import Foundation
print("¡Hola, Mundo!")
// Sin main() explícito en scripts
// En SPM executable: main.swift o @main
// Con @main (Swift 5.3+):
@main
struct App {
static func main() {
print("¡Hola con @main!")
}
}
// Comentarios:
// Línea única
/* Multi-línea
/* ¡anidado! */ */
/// Documentación (aparece en Quick Help)print() es la función de output. Los scripts ejecutan top-level sin main(). @main define el punto de entrada en proyectos SPM. import Foundation para tipos básicos avanzados. Comentarios: //, /* */ (anidables), /// para documentación.
Swift REPL y scripts
// REPL interactivo:
$ swift
Welcome to Swift!
1> let x = 42
2> print(x * 2)
84
3> :quit
// Script (archivo .swift):
// hola.swift
print("¡Hola, Swift!")
let nums = [1, 2, 3]
print(nums.map { $0 * 2 })
// Ejecutar:
swift hola.swift
// Compilar a binario:
swiftc hola.swift -o hola
./hola
// Modo optimizado:
swiftc -O hola.swift -o hola_releaseswift abre el REPL interactivo. swift archivo.swift ejecuta scripts. swiftc compila a binario nativo. -O activa optimizaciones. El REPL es ideal para experimentar. Los scripts no necesitan main() — el código top-level se ejecuta directamente.
Xcode: crear proyecto
// 1. Xcode → File → New → Project // 2. Elegir template: // - App (iOS/macOS) → SwiftUI o UIKit // - Command Line Tool → CLI // - Framework → biblioteca // - Package → SPM // 3. Configurar: // Product Name: MiApp // Organization: com.empresa // Interface: SwiftUI // Language: Swift // 4. Estructura del proyecto: // MiApp/ // ├── MiApp.swift → @main entry // ├── ContentView.swift → UI principal // ├── Assets.xcassets → imágenes/colores // └── Info.plist → configuración // 5. Run: Cmd+R (simulador)
Xcode crea proyectos con templates. SwiftUI es el framework moderno de UI. Cmd+R compila y corre en el simulador. ContentView.swift es la view principal. Assets.xcassets gestiona imágenes y colores. Info.plist tiene la configuración de la app.
Swift Package Manager
// Crear paquete ejecutable: swift package init --type executable cd MiApp // Crear biblioteca: swift package init --type library // Estructura: // Package.swift → manifiesto // Sources/ → código fuente // Tests/ → tests // .build/ → artefactos (gitignore) // Comandos: swift build // compilar swift run // compilar + ejecutar swift test // correr tests swift package resolve // resolver dependencias swift package update // actualizar dependencias
swift package init crea un proyecto con estructura estándar. Package.swift es el manifiesto (dependencias, targets). swift build compila, swift run ejecuta. swift test corre los tests. Sustituye Xcode projects para CLI y server-side.
Compilación y optimización
// Compilar archivo único: swiftc main.swift -o app // Múltiples archivos: swiftc *.swift -o app // Optimizaciones: swiftc -O main.swift -o app // optimizar swiftc -Ounchecked main.swift // sin checks (¡riesgo!) // Debug info: swiftc -g main.swift -o app // Módulos: swiftc -emit-module -module-name MiLib *.swift // Ver tiempo de compilación: swiftc -Xfrontend -debug-time-function-bodies main.swift // Whole Module Optimization (SPM): // swift build -c release
swiftc es el compilador. -O activa optimizaciones (release). -g añade debug info. -Ounchecked elimina verificaciones (peligroso). swift build -c release compila con WMO. Swift compila a código nativo vía LLVM.
SwiftUI e Ecossistema
SwiftUI: primera view
import SwiftUI
struct ContentView: View {
var body: some View {
VStack(spacing: 16) {
Text("¡Hola, SwiftUI!")
.font(.largeTitle)
.foregroundColor(.blue)
Button("Tocar") {
print("¡Tocado!")
}
.buttonStyle(.borderedProminent)
}
.padding()
}
}
// App entry:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}SwiftUI es declarativo — describe la UI, no imperativo. body devuelve some View. VStack/HStack/ZStack para layout. Modificadores encadenables (.font, .padding). @main + WindowGroup es el entry point.
URLSession (networking)
// GET con async/await:
func fetchUsers() async throws -> [User] {
let url = URL(string: "https://api.com/users")!
let (data, response) = try await URLSession.shared
.data(from: url)
guard let http = response as? HTTPURLResponse,
http.statusCode == 200 else {
throw NetworkError.servidor(code: 500)
}
return try JSONDecoder().decode([User].self, from: data)
}
// POST:
func crear(user: User) async throws {
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.setValue("application/json",
forHTTPHeaderField: "Content-Type")
request.httpBody = try JSONEncoder().encode(user)
let _ = try await URLSession.shared.data(for: request)
}URLSession.shared.data(from:) con async/await. Verificar HTTPURLResponse y el status code. JSONDecoder para el parse. POST: definir httpMethod, headers y httpBody. Siempre tratar errores con throws. Sustituye completion handlers.
Deploy y distribución
// iOS App Store: // 1. Xcode → Product → Archive // 2. Organizer → Distribute App // 3. App Store Connect (upload) // 4. TestFlight (beta testing) // Certificados y Profiles: // - Apple Developer Account ($99/año) // - Signing & Capabilities (Xcode) // - Provisioning Profiles // macOS: // - Mac App Store o notarización // - xcrun notarytool submit app.zip // CI/CD: // xcodebuild -scheme MyApp -sdk iphonesimulator // xcodebuild test -scheme MyApp -destination "platform=iOS Simulator,name=iPhone 16" // fastlane: // fastlane beta // upload TestFlight
La distribución iOS requiere Apple Developer Account. Archive + Organizer para el upload. TestFlight para beta. Signing con certificados y profiles. macOS: notarización con notarytool. CI/CD con xcodebuild o fastlane.
@State y @Binding
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("Cuenta: \(count)")
Button("+1") { count += 1 }
}
}
}
// @Binding: pasar estado al hijo
struct ChildView: View {
@Binding var value: Int
var body: some View {
Button("Reset") { value = 0 }
}
}
// Uso:
struct ParentView: View {
@State private var count = 0
var body: some View {
ChildView(value: $count) // $ = binding
}
}@State gestiona el estado local de la view (private). Los cambios re-renderizan automáticamente. @Binding pasa una referencia mutable al hijo. $count crea el binding. State es la fuente de verdad. Siempre private en @State.
Combine (reactivo)
import Combine
class ViewModel: ObservableObject {
@Published var searchText = ""
@Published var results: [String] = []
private var cancellables = Set<AnyCancellable>()
init() {
$searchText
.debounce(for: .milliseconds(300), scheduler: RunLoop.main)
.removeDuplicates()
.filter { !$0.isEmpty }
.sink { [weak self] query in
self?.search(query)
}
.store(in: &cancellables)
}
func search(_ q: String) {
results = ["Resultado: \(q)"]
}
}Combine es el framework reactivo de Apple. @Published emite cambios. debounce espera una pausa. removeDuplicates evita repeticiones. sink se suscribe. store(in:) mantiene viva la suscripción. Alternativa: AsyncSequence (más moderno).
Navigation
struct ListView: View {
let items = ["Swift", "SwiftUI", "Combine"]
var body: some View {
NavigationStack {
List(items, id: \.self) { item in
NavigationLink(value: item) {
Text(item)
}
}
.navigationTitle("Tecnologías")
.navigationDestination(for: String.self) { item in
DetailView(título: item)
}
}
}
}
struct DetailView: View {
let título: String
var body: some View {
Text(título)
.navigationTitle(título)
}
}NavigationStack (iOS 16+) gestiona la navegación. NavigationLink(value:) + navigationDestination(for:) = navegación type-safe. List para listas scrollables. navigationTitle define el título. Sustituye NavigationView (deprecated).
Testing
import Testing
// swift-testing (Swift 6 / Xcode 16):
@Test func sumaCorrecta() {
#expect(suma(2, 3) == 5)
}
@Test("Validación de email")
func emailValido() {
#expect(validar("ana@mail.com") == true)
#expect(validar("invalido") == false)
}
// XCTest (clásico):
import XCTest
class MathTests: XCTestCase {
func testSuma() {
XCTAssertEqual(suma(2, 3), 5)
}
func testError() throws {
XCTAssertThrowsError(try parse("")) { error in
XCTAssertEqual(error as? ParseError, .vacio)
}
}
}swift-testing es el framework moderno (macros, @Test, #expect). XCTest es el clásico (XCTAssertEqual). Ambos corren con swift test o Xcode. Testear edge cases, errores e integración. @Test con descripción para claridad.
Codable (JSON)
struct User: Codable {
let id: Int
let nombre: String
let email: String
}
// Decode (JSON → Struct):
let json = """
{"id": 1, "nombre": "Ana", "email": "ana@mail.com"}
""".data(using: .utf8)!
let user = try JSONDecoder().decode(User.self, from: json)
// Encode (Struct → JSON):
let data = try JSONEncoder().encode(user)
let str = String(data: data, encoding: .utf8)
// CodingKeys (mapear nombres):
struct Post: Codable {
let título: String
let fechaCreacion: Date
enum CodingKeys: String, CodingKey {
case título = "title"
case fechaCreacion = "created_at"
}
}Codable = Encodable + Decodable. JSONDecoder convierte JSON → struct. JSONEncoder hace lo inverso. CodingKeys mapea nombres diferentes. Automático para tipos simples. Esencial para APIs REST y persistencia.
SwiftData (persistencia)
import SwiftData
@Model
class Post {
var título: String
var cuerpo: String
var fechaCreacion: Date
init(título: String, cuerpo: String) {
self.título = título
self.cuerpo = cuerpo
self.fechaCreacion = .now
}
}
// En la App:
@main
struct MyApp: App {
var body: some Scene {
WindowGroup { ContentView() }
.modelContainer(for: Post.self)
}
}
// En la View:
struct ListView: View {
@Query(sort: \Post.fechaCreacion, order: .reverse)
var posts: [Post]
@Environment(\.modelContext) var context
func add() {
context.insert(Post(título: "Nuevo", cuerpo: "..."))
}
}SwiftData (iOS 17+) es el ORM moderno de Apple. @Model define entidades. @Query búsqueda con sorting/filtering. modelContext para insert/delete. .modelContainer(for:) configura. Sustituye Core Data con sintaxis SwiftUI nativa.