Guía de Swift: De 0 a 100

Guía de Swift: De 0 a 100

Desde la primera línea de código hasta producción: instalación, ecosistema, frameworks, proyectos reales y recursos en español para el ecosistema Apple.

FECHA:
ACTUALIZADO:
AUTOR:Jorge Beneyto Castelló
LECTURA:43 MINUTOS DE LECTURA

Guía de Swift: De 0 a 100

¿Qué es Swift?

Swift es un lenguaje de programación compilado, multiparadigma y de tipado fuerte, creado por Chris Lattner en Apple y lanzado por primera vez en 2014. Utiliza LLVM como infraestructura de compilación, lo que le permite generar código nativo optimizado para los procesadores de Apple (ARM64, x86_64) y también para Linux y Windows. Swift reemplazó a Objective-C como lenguaje principal del ecosistema Apple, ofreciendo mayor seguridad de memoria, sintaxis moderna y mejor rendimiento.

¿Dónde se usa?

  • iOS / iPadOS: UIKit, SwiftUI — aplicaciones nativas para iPhone y iPad
  • macOS / Catalyst: AppKit, SwiftUI — apps de escritorio y Catalyst (iPad → Mac)
  • watchOS: WatchKit, SwiftUI — aplicaciones para Apple Watch
  • tvOS: TVML, SwiftUI — apps para Apple TV
  • Server-side: Vapor, Hummingbird, Queues — APIs REST, GraphQL, WebSockets
  • Scripting / CLI: SPM permite crear herramientas de línea de comandos nativas

¿Quién lo usa? Uber (migración de Objective-C a Swift), Airbnb, Lyft, Slack, Telegram, WhatsApp (iOS), LinkedIn. Empresas que necesitan rendimiento nativo en dispositivos Apple.

Philosophía del lenguaje:

  • Protocol-Oriented Programming: Swift prioriza protocolos y structs sobre clases y herencia
  • Safety: opcionales eliminan los null pointer exceptions, tipo fuerte evita errores de tipo en compilación
  • Performance: value types evitan overhead de referencia, compilación con LLVM optimiza al máximo
  • Clarity: sintaxis expresiva con nombres de parámetros externos, guard, defer

Fuente: Swift.org — About, Wikipedia — Swift, Chris Lattner — Swift History


Prerrequisitos

Antes de empezar, necesitas:

  • Conocimientos básicos de terminal: navegar directorios, ejecutar comandos, usar un editor de texto
  • El lenguaje/herramienta instalado en tu sistema (sigue la sección de Instalación si aún no lo tienes)

Si no cumples algún requisito, no te preocupes: cada sección te guiará paso a paso.

¿Cómo empezar? Instalación en todos los entornos

macOS (el entorno nativo)

  1. Descarga Xcode desde la App Store (gratuito, ~8 GB). Incluye:
    • Compilador de Swift (swiftc)
    • Interface Builder (diseño visual de UIs)
    • Simuladores de iOS, iPadOS, watchOS, tvOS
    • Instruments (profiler)
    • Swift Playgrounds interactivos
  2. Alternativa ligera: instala solo Command Line Tools: xcode-select --install (incluye swiftc, lldb, sin Xcode completo)
  3. Verifica: swift --version en terminal

Linux (Ubuntu/Debian)

# Descarga el toolchain de swift.org
wget https://download.swift.org/swift-6.0-release/ubuntu2404/swift-6.0-RELEASE/swift-6.0-RELEASE-ubuntu24.04.tar.gz
tar xzf swift-6.0-RELEASE-ubuntu24.04.tar.gz
sudo mv swift-6.0-RELEASE-ubuntu24.04 /usr/share/swift
echo 'export PATH=/usr/share/swift/usr/bin:$PATH' >> ~/.bashrc
source ~/.bashrc

Dependencias necesarias:

sudo apt update
sudo apt install clang libicu-dev libcurl4-openssl-dev libssl-dev git

O usa el script oficial de instalación:

curl -sL https://swift.org/install.sh | bash

Docker

FROM swift:6.0
WORKDIR /app
COPY Package.swift ./
COPY Sources ./Sources
RUN swift build -c release
CMD [".build/release/mi-app"]

Usa swift:6.0 como imagen base. Para producción, usa multi-stage build con swift:6.0-slim como imagen final.

Windows

  • Swift está disponible para Windows desde Swift 5.3+ (toolchain oficial)
  • Descarga desde swift.org/download — instalador MSI
  • Requiere Visual Studio 2022 con “Desktop development with C++”
  • Verifica: swift --version en PowerShell

Homebrew (herramientas auxiliares)

brew install swiftlint swiftformat  # Linter y formateador
brew install vapor                  # CLI de Vapor (server-side)
brew install tuist                  # Generación de proyectos Xcode

Alias útiles para el día a día

alias sw='swift'
alias swb='swift build'
alias swt='swift test'
alias swr='swift run'
alias spm='swift package'
alias xc='xcodebuild'
alias xct='xcodebuild test -scheme'
alias swlint='swiftlint'
alias swfmt='swiftformat'

Fuente: Swift.org — Downloads, Vapor docs, Swift on Windows


Escala de aprendizaje 0-100 (7 niveles)

Nivel 0-15: Fundamentos absolutos

Conceptos: sintaxis basica, let/var, tipos basicos, if/else, for-in, funciones, print, cadenas.

Proyecto: calculadora CLI que suma, resta, multiplica y divide con manejo de optionals.

Nivel 15-30: Estructuras de datos y funciones avanzadas

Conceptos: arrays, sets, dictionaries, closures, map/filter/reduce, optionals, guard.

Proyecto: gestor de tareas (TODO) con persistencia JSON usando Codable.

Nivel 30-45: Protocolos y diseno orientado a protocolos

Conceptos: protocolos, extensions, protocol extensions, structs con mutating, enums con valores asociados, genericos.

Proyecto: sistema de notificaciones que usa protocolos para diferentes canales (email, SMS, push).

Nivel 45-60: Testing, dependencias y concurrencia basica

Conceptos: XCTest, SPM, DispatchQueue, async/await basico, error handling con throws.

Proyecto: libreria de validacion con tests exhaustivos y documentacion.

Nivel 60-75: SwiftUI, Combine y APIs

Conceptos: SwiftUI, Combine, URLSession, Codable, property wrappers.

Proyecto: app de clima con SwiftUI que consume API REST, maneja errores.

Nivel 75-90: Actores, SwiftData, produccion

Conceptos: actors, Sendable, SwiftData/CoreData, CI/CD, profiling con Instruments.

Proyecto: app de notas con SwiftUI + SwiftData, tests, y CI configurado.

Nivel 90-100: Arquitectura y apps complejas

Conceptos: TCA (The Composable Architecture), testing de UI, performance tuning, macros.

Proyecto: app de gestion de proyectos con multiples pantallas, persistencia, networking y tests.


Primeros pasos y configuración del entorno

Si prefieres ver directamente el “Hola Mundo” en Swift, consulta El Atlas del Hola Mundo.

El REPL

Swift incluye un intérprete interactivo. Ejecuta swift en terminal:

1> print("Hola mundo")
2> let x = 42
3> x + 8
4> [1, 2, 3].map { $0 * 2 }

Para salir: :quit o Ctrl+D. También puedes usar swift repl si el comando swift no entra en modo REPL automáticamente.

Scripts sueltos con swiftc

Swift puede compilar y ejecutar archivos individuales:

// hola.swift
func main() {
    print("Hola mundo")
}
main()

Ejecuta: swift hola.swift (interpretado) o swiftc -o hola hola.swift && ./hola (compilado)

Swift Package Manager (SPM)

SPM es el gestor de dependencias oficial, integrado en el compilador:

# Crear proyecto ejecutable
swift package init --type executable
# Crear proyecto librería
swift package init --type library
# Compilar
swift build
# Ejecutar tests
swift test
# Correr el ejecutable
swift run

Estructura Package.swift

// swift-tools-version: 6.0
import PackageDescription

let package = Package(
    name: "MiProyecto",
    platforms: [
        .macOS(.v14), .iOS(.v17)
    ],
    dependencies: [
        .package(url: "https://github.com/vapor/vapor", from: "4.92.0"),
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
    ],
    targets: [
        .executableTarget(
            name: "MiProyecto",
            dependencies: [
                .product(name: "Vapor", package: "vapor"),
                .product(name: "ArgumentParser", package: "swift-argument-parser"),
            ]
        ),
        .testTarget(
            name: "MiProyectoTests",
            dependencies: ["MiProyecto"]
        ),
    ]
)

Xcode Playgrounds

Perfectos para experimentar sin crear un proyecto:

  1. Abre Xcode → File → New → Playground
  2. Elige iOS o macOS blank playground
  3. Escribe código y ve los resultados en la barra lateral derecha
  4. Los playgrounds ejecutan código de forma interactiva, mostrando el historial de valores

Editores recomendados

Los IDEs y editores más usados son VSCode, JetBrains y Neovim. Extensiones y configuraciones recomendadas.

Configuración inicial del proyecto

mkdir MiProyecto && cd MiProyecto
swift package init --type executable
swift build

Archivo .swiftlint.yml básico:

disabled_rules:
  - trailing_whitespace
opt_in_rules:
  - empty_count
  - force_unwrapping
line_length: 120
identifier_name:
  min_length: 2
  max_length: 40

Fuente: Swift.org — SPM, SwiftLint docs


Paradigmas de programación

Swift es un lenguaje multiparadigma con énfasis en Protocol-Oriented Programming (POP). Puedes mezclar paradigmas según el problema.

ParadigmaSoporte en Swift
ImperativoStatements, loops, mutabilidad con var
FuncionalInmutabilidad con let, map/filter/reduce, closures
POOClases, herencia, protocolos, extensiones
Protocol-OrientedProtocolos + extensiones + structs (POP)
ReactivoCombine (Apple), RxSwift

Imperativo

Swift tiene statements clásicos con sintaxis moderna:

let x = cond ? 10 : 20
for i in 0..<5 { print(i) }
var y = 0
while y < 10 { y += 1 }

Funcional

Swift adopta programación funcional con:

  • Inmutabilidad por defecto: let crea constantes, var para mutables
  • Closures como ciudadanos de primera clase: { (x: Int) -> Int in x * 2 }
  • Higher-order functions: map, filter, reduce, compactMap, flatMap
  • Optionals como mónadas: map, flatMap en Optionals
let pares = (0..<10).filter { $0 % 2 == 0 }.map { $0 * 2 }
let resultado = Optional(10).map { $0 * 2 }.flatMap { $0 > 5 ? Optional($0) : nil }

Protocol-Oriented Programming

Swift prioriza protocolos sobre herencia:

protocol Volador { func volar() }
struct Pajaro: Volador { func volar() { print("bate alas") } }
struct Avion: Volador { func volar() { print("enciende motores") } }

Reactivo (Combine)

Combine proporciona programación reactiva con publishers/subscribers:

import Combine
let publisher = Just(42)
let cancellable = publisher.sink { valor in print(valor) }

Fuente: Swift.org — Protocols, Apple — Combine


Tipos de datos y variables

Value Types vs Reference Types

¿Qué es?

Swift distingue entre value types (structs, enums) que se copian al asignarse, y reference types (classes, actors) que se comparten por referencia. Esta distinción es fundamental para entender la gestión de memoria en Swift.

🔗 Swift Book — Value and Reference Types

Sintaxis básica

// Value type: struct se copia
struct Punto { var x: Double; var y: Double }
var p1 = Punto(x: 0, y: 0)
var p2 = p1 // copia
p2.x = 10
print(p1.x) // 0 (no afecta a p1)

// Reference type: class se comparte
class Persona { var nombre: String; init(nombre: String) { self.nombre = nombre } }
let persona1 = Persona(nombre: "Alice")
let persona2 = persona1 // referencia
persona2.nombre = "Bob"
print(persona1.nombre) // "Bob" (afecta a persona1)

🧪 Cómo probarlo

import XCTest

class ValueVsReferenceTests: XCTestCase {
    func testValueTypeCopy() {
        struct Point { var x: Int }
        var a = Point(x: 5)
        var b = a
        b.x = 10
        XCTAssertEqual(a.x, 5)
        XCTAssertEqual(b.x, 10)
    }

    func testReferenceTypeShared() {
        class Obj { var val = 0 }
        let a = Obj()
        let b = a
        b.val = 42
        XCTAssertEqual(a.val, 42)
    }
}

💡 Memoria y rendimiento

  • Value types en stack: cero overhead de garbage collection
  • Reference types en heap: ARC gestiona la memoria automáticamente
  • Copiar structs grandes tiene coste — usa inout o clases cuando sea necesario
  • Las structs son más predecibles en rendimiento (sin pausas de ARC)

✅ Buenas prácticas

  • ✅ Usa structs por defecto (value types)
  • ✅ Usa clases solo cuando necesites herencia o identidad
  • ⚠️ Copiar structs muy grandes (> 1000 bytes) tiene coste — considera clases
  • ❌ No uses clases para tipos simples (Punto, Tamaño, Fecha)

🔗 Para saber más

Optionals

¿Qué es?

Optional<T> representa un valor que puede ser nil o contener un valor de tipo T. Es la alternativa de Swift a null pointers, y elimina los null pointer exceptions en tiempo de compilación (Optional es un enum: .some(T) o .none).

🔗 Swift Book — Optionals

Sintaxis básica

var nombre: String? = "Alice"
nombre = nil

// Optional binding
if let nombre = nombre {
    print("Hola, \(nombre)")
}

// Guard
guard let nombre = nombre else { return }

// Nil-coalescing
let saludo = nombre ?? "Invitado"

// Optional chaining
let longitud = nombre?.count

// Force unwrap (solo cuando estés 100% seguro)
let valor = nombre!

🧪 Cómo probarlo

import XCTest

class OptionalsTests: XCTestCase {
    func testOptionalBinding() {
        let val: Int? = 42
        var result = 0
        if let v = val { result = v }
        XCTAssertEqual(result, 42)
    }

    func testNilCoalescing() {
        let a: Int? = nil
        XCTAssertEqual(a ?? 0, 0)
    }

    func testOptionalChaining() {
        let s: String? = "hola"
        XCTAssertEqual(s?.count, 4)
    }
}

💡 Memoria y rendimiento

  • Optional<T> ocupa 1 byte extra (discriminant) + tamaño de T
  • Con optimización de puntero nulo: Optional<SomeClass> tiene el mismo tamaño que SomeClass?
  • Force unwrap causa panic en runtime si es nil — comprobado en compilación solo con binding

✅ Buenas prácticas

  • ✅ Usa if let / guard let para desempaquetar optionals
  • ✅ Usa ?? para valores por defecto
  • ⚠️ No uses force unwrap (!) a menos que estés absolutamente seguro
  • ❌ No uses if let innecesariamente — considera guard let para early exit

🔗 Para saber más

Control de flujo y modularidad

Condicionales: if/else y guard

¿Qué es?

Swift ofrece if/else como en otros lenguajes, pero añade guard para early exit y pattern matching en condiciones con if case. guard es especialmente útil para validar condiciones y salir temprano.

🔗 Swift Book — Control Flow

Sintaxis básica

let x = 5
if x > 0 {
    print("positivo")
} else if x < 0 {
    print("negativo")
} else {
    print("cero")
}

// guard: early exit
func procesar(nombre: String?) {
    guard let nombre = nombre else { return }
    print("Hola, \(nombre)")
}

// if case: pattern matching en condiciones
let valor: Int? = 42
if case .some(let v) = valor {
    print(v)
}

🧪 Cómo probarlo

import XCTest

class ControlFlowTests: XCTestCase {
    func testIfElse() {
        let x = 5
        let res = x > 0 ? "positivo" : "no positivo"
        XCTAssertEqual(res, "positivo")
    }

    func testGuard() {
        func getLength(_ s: String?) -> Int {
            guard let s = s else { return 0 }
            return s.count
        }
        XCTAssertEqual(getLength("hola"), 4)
        XCTAssertEqual(getLength(nil), 0)
    }
}

💡 Memoria y rendimiento

  • guard no tiene overhead: es equivalente a un if con return
  • El compilador optimiza ambas formas al mismo código máquina
  • if case se compila a una comparación de enum tan eficiente como un switch

✅ Buenas prácticas

  • ✅ Usa guard para validaciones al inicio de funciones (golden path)
  • ✅ Prefiere operador ternario ? : para asignaciones condicionales simples
  • ⚠️ No uses guard cuando el resto de la función depende del binding
  • ❌ No anides if let profundamente — usa guard para aplanar

🔗 Para saber más

Loops: for-in, while, repeat-while

¿Qué es?

Swift ofrece for-in para iterar colecciones y rangos, while para bucles condicionales, y repeat-while (equivalente a do-while). Los rangos se expresan con ..< (exclusivo) y ... (inclusivo).

🔗 Swift Book — For-In Loops

Sintaxis básica

// for-in con rango
for i in 0..<5 { print(i) }  // 0..4
for i in 0...5 { print(i) }  // 0..5

// for-in con colección
let nombres = ["Alice", "Bob", "Charlie"]
for nombre in nombres { print(nombre) }

// enumerated
for (i, nombre) in nombres.enumerated() {
    print("\(i): \(nombre)")
}

// while
var n = 3
while n > 0 { n -= 1 }

// repeat-while
var m = 3
repeat { m -= 1 } while m > 0

🧪 Cómo probarlo

import XCTest

class LoopsTests: XCTestCase {
    func testForRange() {
        var sum = 0
        for i in 0...5 { sum += i }
        XCTAssertEqual(sum, 15)
    }

    func testWhile() {
        var n = 3
        var result = [Int]()
        while n > 0 { result.append(n); n -= 1 }
        XCTAssertEqual(result, [3, 2, 1])
    }
}

💡 Memoria y rendimiento

  • for-in con rangos se compila a un loop óptimo (igual que C)
  • for-in con colecciones usa el protocolo Sequence/Iterator
  • reversed() no crea una copia: es lazy
  • Los rangos son tipos valor: ocupan 2 enteros en stack

✅ Buenas prácticas

  • ✅ Usa for-in por defecto para iterar colecciones
  • ✅ Usa enumerated() cuando necesites índice y valor
  • ⚠️ No uses for i in 0..<array.count si puedes iterar directamente
  • ❌ No modifiques una colección mientras la iteras

🔗 Para saber más

Switch con pattern matching

¿Qué es?

switch en Swift es extremadamente poderoso: soporta pattern matching, intervalos, tuplas, where clauses, y debe ser exhaustivo.

🔗 Swift Book — Switch

Sintaxis básica

let valor = 2
switch valor {
case 1: print("uno")
case 2: print("dos")
case 3...10: print("varios")
default: print("muchos")
}

let punto = (0, 0)
switch punto {
case (0, 0): print("origen")
case (_, 0): print("en eje X")
case (0, _): print("en eje Y")
default: print("otro")
}

let par = (1, 1)
switch par {
case let (x, y) where x == y: print("iguales")
case let (x, y): print("\(x), \(y)")
}

🧪 Cómo probarlo

import XCTest

class SwitchTests: XCTestCase {
    func testSwitchBasic() {
        let x = 2
        let res: String
        switch x {
        case 1: res = "uno"
        case 2: res = "dos"
        default: res = "otro"
        }
        XCTAssertEqual(res, "dos")
    }
}

💡 Memoria y rendimiento

  • switch se compila a tabla de saltos o comparaciones (como en C)
  • El compilador verifica exhaustividad en compilación
  • Pattern matching con enums es O(1) cuando el discriminant es pequeño

✅ Buenas prácticas

  • ✅ Cubre todos los casos (el compilador lo exige para enums)
  • ✅ Usa default para casos no cubiertos
  • ⚠️ El orden importa: la primera coincidencia se ejecuta
  • ❌ No uses switch con un solo caso — prefiere if o guard

🔗 Para saber más

Módulos y paquetes (SPM)

¿Qué es?

El Swift Package Manager (SPM) es el sistema de compilación y gestión de dependencias oficial. Cada directorio con Package.swift es un módulo (target). SPM resuelve dependencias, compila y ejecuta tests.

🔗 Swift Book — SPM

Sintaxis básica

// Package.swift
// swift-tools-version: 6.0
import PackageDescription

let package = Package(
    name: "MiLibreria",
    platforms: [.macOS(.v14), .iOS(.v17)],
    products: [
        .library(name: "MiLibreria", targets: ["MiLibreria"]),
    ],
    dependencies: [
        .package(url: "https://github.com/apple/swift-argument-parser", from: "1.3.0"),
    ],
    targets: [
        .target(name: "MiLibreria", dependencies: [
            .product(name: "ArgumentParser", package: "swift-argument-parser"),
        ]),
        .testTarget(name: "MiLibreriaTests", dependencies: ["MiLibreria"]),
    ]
)

✅ Buenas prácticas

  • ✅ Usa swift package init para crear nuevos paquetes
  • ✅ Separa en múltiples targets dentro de un mismo paquete
  • ⚠️ No expongas tipos internos como public sin necesidad

🔗 Para saber más


Sistema de archivos (File System I/O)

FileManager

¿Qué es?

FileManager es la clase estándar de Swift para operaciones con el sistema de archivos. Proporciona métodos para leer, escribir, copiar, mover y borrar archivos y directorios. Es parte de Foundation.

🔗 Apple — FileManager

Sintaxis básica

import Foundation

let fm = FileManager.default
let docs = fm.urls(for: .documentDirectory, in: .userDomainMask).first!

// Escribir archivo
let contenido = "Hola mundo"
let url = docs.appendingPathComponent("archivo.txt")
try contenido.write(to: url, atomically: true, encoding: .utf8)

// Leer archivo
let leido = try String(contentsOf: url, encoding: .utf8)
print(leido)

// Listar directorio
let items = try fm.contentsOfDirectory(at: docs, includingPropertiesForKeys: nil)
for item in items { print(item.lastPathComponent) }

// Crear directorio
try fm.createDirectory(at: docs.appendingPathComponent("datos"),
                       withIntermediateDirectories: true)

// Verificar existencia
if fm.fileExists(atPath: url.path) {
    try fm.removeItem(at: url)
}

🧪 Cómo probarlo

import XCTest

class FileManagerTests: XCTestCase {
    func testWriteAndRead() throws {
        let fm = FileManager.default
        let tmp = fm.temporaryDirectory.appendingPathComponent("test.txt")
        try "contenido".write(to: tmp, atomically: true, encoding: .utf8)
        let leido = try String(contentsOf: tmp, encoding: .utf8)
        XCTAssertEqual(leido, "contenido")
        try fm.removeItem(at: tmp)
    }
}

💡 Memoria y rendimiento

  • String(contentsOf:) carga todo el archivo en memoria — no apto para archivos grandes
  • Para archivos grandes, usa InputStream/OutputStream o FileHandle
  • FileManager es thread-safe
  • Las operaciones de FS son lentas (ms) — en iOS, hazlas en background

✅ Buenas prácticas

  • ✅ Usa FileManager.default como singleton
  • ✅ Siempre maneja errores con try/catch
  • ⚠️ En iOS, los archivos están sandboxeados — solo accesibles dentro del app container
  • ❌ No uses String(contentsOf:) para archivos > 10 MB

🔗 Para saber más


Algoritmos y estructuras de datos

Ordenación con sorted()

¿Qué es?

Swift proporciona sorted() para ordenar colecciones. Retorna un nuevo array ordenado (no modifica el original). Para ordenar in-place, usa sort().

🔗 Apple — sorted()

Sintaxis básica

let nums = [3, 1, 4, 1, 5]
let ordenado = nums.sorted()  // [1, 1, 3, 4, 5]

// Ordenación personalizada
let items = [(3, "c"), (1, "a"), (2, "b")]
let porNumero = items.sorted { $0.0 < $1.0 }
let porLetra = items.sorted { $0.1 < $1.1 }

// Con sorted(by:)
let descendente = nums.sorted(by: >)

// Ordenación estable
struct Persona { let nombre: String; let edad: Int }
let personas = [Persona(nombre: "Alice", edad: 30),
                Persona(nombre: "Bob", edad: 25)]
let porEdad = personas.sorted { $0.edad < $1.edad }

🧪 Cómo probarlo

import XCTest

class SortTests: XCTestCase {
    func testSorted() {
        let v = [3, 1, 4, 1, 5]
        XCTAssertEqual(v.sorted(), [1, 1, 3, 4, 5])
    }

    func testSortedCustom() {
        let v = [(3, "c"), (1, "a"), (2, "b")]
        let r = v.sorted { $0.0 < $1.0 }
        XCTAssertEqual(r.map { $0.0 }, [1, 2, 3])
    }
}

💡 Memoria y rendimiento

  • sorted() usa TimSort (estable): O(n log n), O(n) memoria extra
  • sort() (in-place) usa introsort (inestable): O(n log n), O(log n) memoria
  • Swift Arrays son value types: sorted() crea una copia

✅ Buenas prácticas

  • ✅ Usa sorted() cuando necesites preservar el original
  • ✅ Usa sort() para ordenar in-place cuando el original no importa
  • ⚠️ sorted() crea una copia — para arrays grandes, considera sort() in-place

🔗 Para saber más

Map, Filter, Reduce

¿Qué es?

map, filter y reduce son higher-order functions que permiten transformar colecciones de forma declarativa. Son la base del estilo funcional en Swift.

🔗 Apple — Collection

Sintaxis básica

let nums = [1, 2, 3, 4, 5, 6]

let pares = nums.filter { $0 % 2 == 0 }  // [2, 4, 6]
let dobles = nums.map { $0 * 2 }          // [2, 4, 6, 8, 10, 12]
let suma = nums.reduce(0, +)              // 21

// Combinados
let resultado = nums
    .filter { $0 % 2 == 0 }
    .map { $0 * 10 }
    .reduce(0, +)  // (20 + 40 + 60) = 120

// compactMap: elimina nils
let strings = ["1", "a", "2", "b", "3"]
let numeros = strings.compactMap { Int($0) }  // [1, 2, 3]

// flatMap: aplana arrays anidados
let nested = [[1, 2], [3, 4]]
let flat = nested.flatMap { $0 }  // [1, 2, 3, 4]

🧪 Cómo probarlo

import XCTest

class MapFilterReduceTests: XCTestCase {
    func testMap() {
        XCTAssertEqual([1, 2, 3].map { $0 * 2 }, [2, 4, 6])
    }

    func testFilter() {
        XCTAssertEqual([1, 2, 3, 4].filter { $0 % 2 == 0 }, [2, 4])
    }

    func testReduce() {
        XCTAssertEqual([1, 2, 3, 4, 5].reduce(0, +), 15)
    }

    func testCompactMap() {
        let input = ["1", "a", "2"]
        XCTAssertEqual(input.compactMap { Int($0) }, [1, 2])
    }
}

💡 Memoria y rendimiento

  • map/filter/reduce son lazy si usas LazySequence (array.lazy.map { ... })
  • Sin lazy, cada operación crea un nuevo array (coste de memoria)
  • Para cadenas largas, usa lazy para evitar arrays intermedios
  • reduce es eficiente: una sola pasada

✅ Buenas prácticas

  • ✅ Prefiere map/filter sobre loops manuales con lógica simple
  • ✅ Usa compactMap para transformaciones que pueden fallar
  • ⚠️ Para cadenas largas (> 10K elementos), considera lazy
  • ❌ No abuses de reduce para operaciones simples — usa sum() o joined()

🔗 Para saber más

Big O de colecciones estándar

OperaciónArraySetDictionary
AccesoO(1)—O(1)*
BúsquedaO(n)O(1)*O(1)*
InserciónO(1)**O(1)*O(1)*
EliminaciónO(n)O(1)*O(1)*
OrdenaciónO(n log n)——

*Amortizado, asumiendo buen hash **Amortizado O(1); O(n) si necesita redimensionar

🔗 Para saber más


Conceptos clave del lenguaje

Protocolos y Protocol Extensions

¿Qué es?

Los protocolos son la herramienta fundamental de abstracción en Swift (equivalentes a interfaces en otros lenguajes). Las protocol extensions permiten proporcionar implementaciones por defecto. Esta combinación es la base del Protocol-Oriented Programming (POP).

🔗 Swift Book — Protocols

Sintaxis básica

protocol Volador {
    var velocidadMaxima: Double { get }
    func volar()
}

extension Volador {
    func volar() { print("Volando a \(velocidadMaxima) km/h") }
}

struct Pajaro: Volador {
    let velocidadMaxima: Double = 60
}

struct Avion: Volador {
    let velocidadMaxima: Double = 900
}

let p = Pajaro()
p.volar() // "Volando a 60 km/h"

🧪 Cómo probarlo

import XCTest

class ProtocolTests: XCTestCase {
    protocol Saludable { func saludar() -> String }
    extension Saludable { func saludar() -> String { "Hola" } }
    struct Persona: Saludable {}

    func testProtocolExtension() {
        let p = Persona()
        XCTAssertEqual(p.saludar(), "Hola")
    }
}

💡 Memoria y rendimiento

  • Protocolos con protocol (keyword) se despachan dinámicamente (vtable)
  • Protocolos con static requirements usan dispatch directo
  • extension de protocolos proporciona implementación por defecto sin overhead
  • Structs que conforman protocolos no tienen overhead adicional

✅ Buenas prácticas

  • ✅ Prefiere protocolos sobre clases base para abstracción
  • ✅ Usa protocol extensions para implementaciones por defecto
  • ⚠️ No pongas requirements en el protocolo que no necesiten ser sobrescritos
  • ❌ No uses herencia de clases cuando un protocolo + struct es suficiente

🔗 Para saber más

Structs avanzadas: mutating, computed properties, property observers

¿Qué es?

Las structs en Swift soportan mucho más que simples datos: computed properties (propiedades calculadas), property observers (willSet/didSet), mutating methods (métodos que modifican la struct), y lazy properties.

🔗 Swift Book — Properties

Sintaxis básica

struct Rectangulo {
    var ancho: Double
    var alto: Double

    // Computed property
    var area: Double { ancho * alto }

    // Property observer
    var color: String = "blanco" {
        willSet { print("va a cambiar a \(newValue)") }
        didSet { print("cambio de \(oldValue) a \(color)") }
    }

    // Mutating method
    mutating func escalar(_ factor: Double) {
        ancho *= factor
        alto *= factor
    }

    // Lazy property (se calcula al primer acceso)
    lazy var descripcion: String = "\(ancho)x\(alto)"
}

🧪 Cómo probarlo

import XCTest

class StructTests: XCTestCase {
    func testComputedProperty() {
        var r = Rectangulo(ancho: 10, alto: 20)
        XCTAssertEqual(r.area, 200)
    }

    func testMutating() {
        var r = Rectangulo(ancho: 10, alto: 20)
        r.escalar(2)
        XCTAssertEqual(r.ancho, 20)
        XCTAssertEqual(r.alto, 40)
    }
}

🔗 Para saber más

Enums con valores asociados y pattern matching

¿Qué es?

Los enums en Swift son algebraicos: cada caso puede llevar valores asociados (tuplas) o raw values. Combinados con pattern matching, son la herramienta más poderosa para modelar estados.

🔗 Swift Book — Enums

Sintaxis básica

enum Resultado {
    case exito(Int)
    case error(String)
}

// Valores asociados
enum Mensaje {
    case texto(String)
    case coordenadas(x: Double, y: Double)
    case cerrar
}

let msg = Mensaje.texto("Hola")
switch msg {
case .texto(let s): print(s)
case .coordenadas(let x, let y): print("\(x), \(y)")
case .cerrar: print("adios")
}

// Raw values
enum Estado: String {
    case activo = "activo"
    case inactivo = "inactivo"
}
print(Estado.activo.rawValue) // "activo"

🧪 Cómo probarlo

import XCTest

class EnumTests: XCTestCase {
    enum Opcion { case entero(Int); case texto(String) }

    func testEnumPatternMatching() {
        let val = Opcion.entero(42)
        var result = 0
        if case .entero(let v) = val { result = v }
        XCTAssertEqual(result, 42)
    }

    func testEnumRawValue() {
        enum Color: String { case rojo = "rojo" }
        XCTAssertEqual(Color.rojo.rawValue, "rojo")
    }
}

💡 Memoria y rendimiento

  • El tamaño del enum es el del caso más grande + discriminant
  • Enum con Int raw value ocupa el tamaño del entero
  • Pattern matching en enums es tan eficiente como un switch en C
  • Los valores asociados se almacenan inline

✅ Buenas prácticas

  • ✅ Usa enums para modelar estados finitos y opciones
  • ✅ Prefiere enums con valores asociados sobre optionals múltiples
  • ⚠️ No añadas casos sin valores asociados si el caso necesita datos
  • ❌ No uses enums para grupos de constantes — prefiere structs con static let

🔗 Para saber más

Closures

¿Qué es?

Las closures son bloques de código autónomos que pueden capturar referencias del contexto. Son equivalentes a lambdas en otros lenguajes. Swift tiene tres tipos: globales, anidadas y closure expressions.

🔗 Swift Book — Closures

Sintaxis básica

// Closure expression
let multiplicar = { (a: Int, b: Int) -> Int in a * b }
print(multiplicar(3, 4)) // 12

// Trailing closure
let nums = [1, 2, 3, 4]
let pares = nums.filter { $0 % 2 == 0 }

// Capturing values
func makeIncrementer(increment: Int) -> () -> Int {
    var total = 0
    return {
        total += increment
        return total
    }
}
let inc = makeIncrementer(increment: 5)
print(inc()) // 5
print(inc()) // 10

// @escaping: closure que escapa la función
func ejecutarDespues(_ closure: @escaping () -> Void) {
    DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
        closure()
    }
}

🧪 Cómo probarlo

import XCTest

class ClosureTests: XCTestCase {
    func testClosure() {
        let sum = { (a: Int, b: Int) -> Int in a + b }
        XCTAssertEqual(sum(2, 3), 5)
    }

    func testCapturing() {
        func makeCounter() -> () -> Int {
            var count = 0
            return { count += 1; return count }
        }
        let counter = makeCounter()
        XCTAssertEqual(counter(), 1)
        XCTAssertEqual(counter(), 2)
    }
}

💡 Memoria y rendimiento

  • Las closures son objetos en heap: ocupan memoria por las variables capturadas
  • Sin captura: son punteros a función (sin overhead)
  • @escaping implica retención potencial (ARC)
  • Los closures con captura débil ([weak self]) evitan retain cycles

✅ Buenas prácticas

  • ✅ Usa trailing closures para APIs que reciben closures como último parámetro
  • ✅ Usa $0, $1 para closures simples (shorthand argument names)
  • ⚠️ Captura [weak self] en closures que puedan crear retain cycles
  • ❌ No captures objetos grandes innecesariamente en closures

🔗 Para saber más

Error handling: throws, try/catch, Result, defer

¿Qué es?

Swift maneja errores con throws/try/catch, que son más seguros que excepciones porque el compilador verifica que los errores se manejen. defer ejecuta código al salir del scope (para limpieza). Result<T, E> es un enum para errores como valores.

🔗 Swift Book — Error Handling

Sintaxis básica

enum MiError: Error {
    case noEncontrado
    case redError(String)
}

func leerArchivo(_ path: String) throws -> String {
    guard !path.isEmpty else { throw MiError.noEncontrado }
    return "contenido"
}

// try? (opcional)
let resultado = try? leerArchivo("")

// try! (force, puede crash)
// let contenido = try! leerArchivo("test.txt")

// do/catch
do {
    let contenido = try leerArchivo("test.txt")
    print(contenido)
} catch MiError.noEncontrado {
    print("no encontrado")
} catch {
    print("error: \(error)")
}

// defer
func procesar() throws {
    let file = open(...)
    defer { close(file) } // se ejecuta al salir
    // ... usar file
}

// Result<T, E>
func dividir(_ a: Int, _ b: Int) -> Result<Int, Error> {
    guard b != 0 else { return .failure(MiError.redError("div por cero")) }
    return .success(a / b)
}

🧪 Cómo probarlo

import XCTest

class ErrorTests: XCTestCase {
    enum SimpleError: Error { case fallo }

    func testThrows() throws {
        func puedeFallar(_ x: Int) throws -> Int {
            if x < 0 { throw SimpleError.fallo }
            return x * 2
        }

        let r = try puedeFallar(5)
        XCTAssertEqual(r, 10)
        XCTAssertThrowsError(try puedeFallar(-1))
    }

    func testResult() {
        let r: Result<Int, Error> = .success(42)
        XCTAssertEqual(try? r.get(), 42)
    }
}

🔗 Para saber más

Genéricos, where constraints, opaque types (some), any (existential)

¿Qué es?

Los genéricos permiten escribir código reutilizable con cualquier tipo. some (opaque types) oculta el tipo concreto pero mantiene la identidad. any (existential types) permite heterogeneidad con boxed protocol types.

🔗 Swift Book — Generics

Sintaxis básica

// Función genérica
func elMayor<T: Comparable>(_ a: T, _ b: T) -> T {
    a > b ? a : b
}

// Struct genérica
struct Par<T, U> { let primero: T; let segundo: U }

// Where constraints
func sonIguales<T: Equatable, U: Equatable>(_ a: T, _ b: U) -> Bool where T == U {
    a == b
}

// Opaque type (some)
func crearNumero() -> some Equatable { 42 }
let x = crearNumero()
// let y = crearNumero() == 42 // Sí, porque ambos son algún Equatable

// Existential type (any)
let valores: [any Equatable] = [1, "hola", true]

🧪 Cómo probarlo

import XCTest

class GenericTests: XCTestCase {
    func testGenericFunction() {
        func id<T>(_ x: T) -> T { x }
        XCTAssertEqual(id(42), 42)
        XCTAssertEqual(id("hola"), "hola")
    }

    func testOpaqueType() {
        func make() -> some Equatable { 42 }
        XCTAssertEqual(make() as! Int, 42)
    }
}

✅ Buenas prácticas

  • ✅ Usa genéricos para código reutilizable sin perder tipo
  • ✅ Usa some para ocultar tipos concretos en APIs públicas
  • ⚠️ any tiene overhead de boxed — úsalo solo cuando necesites heterogeneidad

🔗 Para saber más

Codable (Encodable/Decodable)

¿Qué es?

Codable es un protocolo que combina Encodable y Decodable. Convierte tipos Swift a/desde formatos externos (JSON, Property List, etc.) automáticamente con Codable + JSONEncoder/JSONDecoder.

🔗 Apple — Codable

Sintaxis básica

struct Usuario: Codable {
    let nombre: String
    let edad: Int
    let email: String
}

// Codificar a JSON
let usuario = Usuario(nombre: "Alice", edad: 30, email: "alice@ex.com")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let jsonData = try encoder.encode(usuario)
let jsonString = String(data: jsonData, encoding: .utf8)!

// Decodificar desde JSON
let decoder = JSONDecoder()
let decoded = try decoder.decode(Usuario.self, from: jsonData)

🧪 Cómo probarlo

import XCTest

class CodableTests: XCTestCase {
    struct Item: Codable, Equatable { let id: Int; let name: String }

    func testEncodeDecode() throws {
        let original = Item(id: 1, name: "test")
        let data = try JSONEncoder().encode(original)
        let decoded = try JSONDecoder().decode(Item.self, from: data)
        XCTAssertEqual(original, decoded)
    }

    func testCustomKeys() throws {
        struct Product: Codable {
            let id: Int
            let productName: String
            enum CodingKeys: String, CodingKey {
                case id
                case productName = "product_name"
            }
        }
        let json = "{"id": 1, "product_name": "Widget"}".data(using: .utf8)!
        let product = try JSONDecoder().decode(Product.self, from: json)
        XCTAssertEqual(product.productName, "Widget")
    }
}

🔗 Para saber más

async/await, actors, Sendable

¿Qué es?

Swift Concurrency (iOS 13+/macOS 10.15+) proporciona async/await nativo, actors para estado protegido, y Sendable para transferencia segura entre concurrencias. Es el modelo moderno de concurrencia en Swift.

🔗 Apple — Swift Concurrency

Sintaxis básica

// async/await
func fetchUser(id: Int) async throws -> Usuario {
    let url = URL(string: "https://api.example.com/users/\(id)")!
    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(Usuario.self, from: data)
}

// Actor: protege estado compartido
actor Contador {
    private var valor = 0
    func incrementar() { valor += 1 }
    func obtener() -> Int { valor }
}

let contador = Contador()
await contador.incrementar()
print(await contador.obtener())

// Sendable: tipos seguros para concurrencia
struct Configuracion: Sendable {
    let timeout: Int
}

🧪 Cómo probarlo

import XCTest

class AsyncTests: XCTestCase {
    func testAsyncFunction() async {
        async func obtener() -> Int { 42 }
        let result = await obtener()
        XCTAssertEqual(result, 42)
    }

    func testActor() async {
        actor Accumulator {
            var value = 0
            func add(_ x: Int) { value += x }
            func get() -> Int { value }
        }
        let acc = Accumulator()
        await acc.add(10)
        await acc.add(20)
        XCTAssertEqual(await acc.get(), 30)
    }
}

💡 Memoria y rendimiento

  • async/await es cooperativo: cada tarea usa ~1 KB de overhead
  • Actors protegen el estado con un lock interno (serial executor)
  • Sendable es una restricción de compilación: cero overhead
  • TaskGroup permite paralelismo estructurado

✅ Buenas prácticas

  • ✅ Usa async/await para operaciones de red y E/S
  • ✅ Usa actors para proteger estado mutable compartido
  • ⚠️ No llames código bloqueante dentro de contextos async
  • ❌ No uses DispatchQueue cuando async/await es suficiente

🔗 Para saber más


POO y patrones de diseño

Clases y herencia

¿Qué es?

Swift soporta POO clásica con clases, herencia, overriding y polimorfismo. Sin embargo, el lenguaje prioriza protocolos y structs sobre clases. Usa clases solo cuando necesites herencia o identidad por referencia.

🔗 Swift Book — Classes

Sintaxis básica

class Animal {
    let nombre: String
    init(nombre: String) { self.nombre = nombre }
    func sonido() -> String { "" }
}

class Perro: Animal {
    override func sonido() -> String { "Guau" }
}

class Gato: Animal {
    override func sonido() -> String { "Miau" }
}

let animales: [Animal] = [Perro(nombre: "Rex"), Gato(nombre: "Misu")]
for animal in animales {
    print(animal.sonido()) // polimorfismo dinámico
}

Patrones de diseño

PatrónEn Swift
StrategyProtocolos + structs
ObserverCombine (Publisher/Subscriber)
Singletonstatic let shared = ... + private init
FactoryProtocolo + registry
BuilderResult Builders o métodos encadenados
AdapterProtocol conformance

Builder con Result Builders

@resultBuilder
struct StringBuilder {
    static func buildBlock(_ parts: String...) -> String {
        parts.joined(separator: " ")
    }
}

@StringBuilder func saludo() -> String {
    "Hola"
    "mundo"
    "Swift"
}
print(saludo()) // "Hola mundo Swift"

Singleton

class ConfigManager {
    static let shared = ConfigManager()
    private init() {} // impide crear otras instancias

    var timeout: Int = 30
}

Polimorfismo en detalle

Polimorfismo por protocolos (POP)

¿Qué es?

POP (Protocol-Oriented Programming) es el polimorfismo preferido en Swift. Los protocolos definen interfaces, y las extensions proporcionan implementaciones por defecto. Cualquier tipo (struct, enum, class) puede conformar protocolos.

🔗 Swift Book — Protocols

Sintaxis básica

protocol Describible {
    var descripcion: String { get }
}

extension Describible {
    var descripcion: String { "Algo" }
}

struct Persona: Describible {
    let nombre: String
    var descripcion: String { "Persona: \(nombre)" }
}

struct Cosa: Describible {}

let items: [any Describible] = [Persona(nombre: "Alice"), Cosa()]
for item in items { print(item.descripcion) }

Polimorfismo por herencia (clásico)

class Figura {
    func area() -> Double { 0 }
}

class Circulo: Figura {
    let radio: Double
    init(radio: Double) { self.radio = radio }
    override func area() -> Double { Double.pi * radio * radio }
}

Polimorfismo con genéricos y where constraints

func areaTotal<T: Figura>(_ figuras: [T]) -> Double {
    figuras.reduce(0) { $0 + $1.area() }
}

Polimorfismo con Opaque Types (some)

func crearForma() -> some Figura { Circulo(radio: 5) }

Interacción con contenido multimedia

Procesamiento de imágenes

¿Qué es?

Swift tiene soporte nativo para imágenes a través de UIImage (iOS) / NSImage (macOS) y Core Image para filtros.

Sintaxis básica

import UIKit

// Cargar imagen
guard let imagen = UIImage(named: "foto") else { return }

// Redimensionar
let nuevoTamano = CGSize(width: 800, height: 600)
let renderer = UIGraphicsImageRenderer(size: nuevoTamano)
let redimensionada = renderer.image { _ in
    imagen.draw(in: CGRect(origin: .zero, size: nuevoTamano))
}

// Filtros con Core Image
let ciImagen = CIImage(image: "img/guia_0_100_swift/guia_0_100_swift_cover-1200.webp"
let filtro = CIFilter(name: "CISepiaTone")
filtro?.setValue(ciImagen, forKey: kCIInputImageKey)
filtro?.setValue(0.8, forKey: kCIInputIntensityKey)
let resultado = filtro?.outputImage

🔗 Para saber más


Bases de datos

CoreData

¿Qué es?

CoreData es el framework de persistencia de Apple. Es un ORM graph-based que maneja el ciclo de vida de objetos, relaciones, y persistencia (SQLite por defecto). Ideal para aplicaciones iOS/macOS complejas.

🔗 Apple — CoreData

SwiftData (moderno, Swift native)

¿Qué es?

SwiftData (iOS 17+/macOS 14+) es el reemplazo moderno de CoreData. Usa macros de Swift para definir el esquema de datos y @Model para marcar clases persistibles.

🔗 Apple — SwiftData

Fluent (Vapor ORM)

¿Qué es?

Fluent es el ORM de Vapor para Swift server-side. Soporta PostgreSQL, MySQL, SQLite. Define modelos con protocolos y migraciones programáticas.

🔗 Vapor — Fluent


WebSockets

URLSessionWebSocketTask

¿Qué es?

URLSessionWebSocketTask es la API nativa de WebSocket en iOS 13+/macOS 10.15+. Permite comunicación bidireccional con el servidor usando URLSession.

🔗 Apple — WebSocket

Sintaxis básica

import Foundation

let url = URL(string: "wss://echo.websocket.org")!
let session = URLSession.shared
let ws = session.webSocketTask(with: url)

// Enviar mensaje
ws.send(.string("Hola servidor")) { error in
    if let error = error { print(error) }
}

// Recibir mensaje
ws.receive { result in
    switch result {
    case .success(let mensaje):
        switch mensaje {
        case .string(let texto): print(texto)
        case .data(let datos): print(datos)
        @unknown default: break
        }
    case .failure(let error): print(error)
    }
}

ws.resume()

🔗 Para saber más


Concurrencia y paralelismo

async/await (concurrencia estructurada moderna)

¿Qué es?

La concurrencia moderna de Swift con async/await, TaskGroup y structured concurrency. Es el modelo recomendado para todo código nuevo.

🔗 Apple — Concurrency

Sintaxis básica

// TaskGroup: paralelismo estructurado
func procesarMultiples() async throws -> [String] {
    try await withThrowingTaskGroup(of: String.self) { group in
        for id in 1...5 {
            group.addTask { await fetchUser(id: id).nombre }
        }
        var resultados: [String] = []
        for try await resultado in group {
            resultados.append(resultado)
        }
        return resultados
    }
}

// Task: unidad de trabajo asíncrono
Task {
    let result = await fetchUser(id: 1)
    print(result)
}

Actors

¿Qué es?

Los actors protegen el estado mutable compartido con un serial executor. Garantizan que solo una tarea acceda al estado a la vez.

🔗 Apple — Actors

Combine

¿Qué es?

Combine (Apple, iOS 13+) es un framework reactivo que proporciona Publishers, Subscribers y Operators para manejar flujos de datos asíncronos.

🔗 Apple — Combine

Tabla de decisión: async/await vs Combine vs DispatchQueue

async/awaitCombineDispatchQueue
PropósitoOperaciones asíncronasFlujos de datos reactivosTareas en cola
ComplejidadBajaMediaBaja-media
CancelaciónTask.cancel()AnyCancellableN/A
Testingasync XCTestTestSchedulerXCTestExpectation
Uso típicoRed, BD, E/SUI, notificacionesTareas en background

Testing y calidad

Frameworks de testing

Swift tiene XCTest como framework nativo de testing. Otras herramientas incluyen Quick/Nimble (BDD) y Swift Testing (Swift 6+).

FrameworkPropositoAsync?CLI
XCTestTesting nativo de AppleSi (async XCTest)swift test
Quick + NimbleBDD (Behavior Driven Dev)Siswift test
Swift TestingModerno (Swift 6+, swift-testing)Siswift test

Sintaxis basica

import XCTest

final class CalculadoraTests: XCTestCase {
    func testSuma() {
        let resultado = 2 + 2
        XCTAssertEqual(resultado, 4)
    }

    func testDivisionPorCero() {
        func dividir(_ a: Int, _ b: Int) -> Int? {
            guard b != 0 else { return nil }
            return a / b
        }
        XCTAssertNil(dividir(10, 0))
        XCTAssertEqual(dividir(10, 2), 5)
    }

    func testAsyncOperation() async {
        async func obtener() -> Int { 42 }
        let resultado = await obtener()
        XCTAssertEqual(resultado, 42)
    }

    func testPerformance() {
        measure {
            _ = (0..<1000).map { $0 * 2 }
        }
    }
}

💡 Memoria y rendimiento

  • XCTest ejecuta tests en paralelo por clase por defecto
  • Cada test case se ejecuta en instancia separada de XCTestCase
  • measure ejecuta el bloque 10 veces y promedia el tiempo

✅ Buenas practicas

  • ✅ Escribe tests para toda funcion publica y logica critica
  • ✅ Usa setUp/tearDown para configuracion/limpieza repetitiva
  • ⚠️ No tests propiedad privada directamente
  • ❌ No escribas tests que dependan del orden de ejecucion

🔗 Para saber mas


Conceptos avanzados

Property Wrappers

¿Que es?

Los Property Wrappers anaden logica reutilizable a las propiedades. Se definen con @propertyWrapper.

@propertyWrapper
struct Minimo<T: Comparable> {
    private var valor: T
    private let minimo: T

    init(wrappedValue: T, minimo: T) {
        self.valor = wrappedValue
        self.minimo = minimo
    }

    var wrappedValue: T {
        get { valor }
        set { valor = max(newValue, minimo) }
    }
}

struct Configuracion {
    @Minimo(minimo: 0) var timeout: Int = 30
    @Minimo(minimo: 1) var intentos: Int = 3
}

Macros (Swift 5.9+)

Las macros (Swift 5.9+) permiten generar codigo en compilacion. @Observable, @Model (SwiftData) son macros.

SwiftUI

SwiftUI es el framework declarativo de UI de Apple.

import SwiftUI

struct ContentView: View {
    @State private var contador = 0

    var body: some View {
        VStack {
            Text("Contador: \(contador)")
                .font(.title)
            Button("Incrementar") {
                contador += 1
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(8)
        }
        .padding()
    }
}

🔗 Para saber mas


Proyecto final integrador: Procesador de logs

Un procesador de logs que combina file system, algoritmos, protocolos, testing y concurrencia.

import Foundation

enum Nivel: String, Codable {
    case info = "INFO"
    case warn = "WARN"
    case error = "ERROR"
}

struct LogEntry: Codable {
    let nivel: Nivel
    let mensaje: String
    let linea: Int
    let timestamp: Date
}

protocol LogProcessor {
    func procesar(archivo: URL) throws -> [LogEntry]
    func reportar(entries: [LogEntry]) -> [Nivel: Int]
}

class SimpleLogProcessor: LogProcessor {
    func procesar(archivo: URL) throws -> [LogEntry] {
        let contenido = try String(contentsOf: archivo, encoding: .utf8)
        let lineas = contenido.split(separator: "\n")

        return lineas.enumerated().compactMap { (i, linea) in
            let partes = linea.split(separator: ":", maxSplits: 1)
            guard partes.count == 2,
                  let nivel = Nivel(rawValue: String(partes[0]).trimmingCharacters(in: .punctuationCharacters)) else {
                return nil
            }
            return LogEntry(nivel: nivel, mensaje: String(partes[1]).trimmingCharacters(in: .whitespaces),
                          linea: i + 1, timestamp: Date())
        }
    }

    func reportar(entries: [LogEntry]) -> [Nivel: Int] {
        Dictionary(grouping: entries, by: { $0.nivel }).mapValues { $0.count }
    }
}

Tests

import XCTest

final class LogProcessorTests: XCTestCase {
    func testProcesarArchivo() throws {
        let lineas = ["[INFO]: inicio", "[ERROR]: fallo"]
        let contenido = lineas.joined(separator: "\n")
        let url = FileManager.default.temporaryDirectory
            .appendingPathComponent("test.log")
        try contenido.write(to: url, atomically: true, encoding: .utf8)

        let processor = SimpleLogProcessor()
        let entries = try processor.procesar(archivo: url)
        XCTAssertEqual(entries.count, 2)
        XCTAssertEqual(entries[0].nivel, .info)
        XCTAssertEqual(entries[1].nivel, .error)

        let report = processor.reportar(entries: entries)
        XCTAssertEqual(report[.info], 1)
        XCTAssertEqual(report[.error], 1)

        try FileManager.default.removeItem(at: url)
    }
}

Despliegue a producción

Build para producción

swift build -c release

Docker multi-stage

# Stage 1: Build
FROM swift:6.0 AS build
WORKDIR /app
COPY Package.swift ./
RUN swift package resolve
COPY . .
RUN swift build -c release --static-swift-stdlib

# Stage 2: Runtime
FROM ubuntu:24.04
WORKDIR /app
RUN apt-get update && apt-get install -y libicu-dev libxml2 && rm -rf /var/lib/apt/lists/*
COPY --from=build /app/.build/release /app
EXPOSE 8080
CMD ["./App"]

Hosting

CI/CD (GitHub Actions)

name: Deploy

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: swift-actions/setup-swift@v2
      - run: swift build -c release

Canales y recursos en espanol

YouTube

Comunidades

Repositorios GitHub


Hacks y tips de productividad

1. swift test —filter

swift test --filter ProcesadorTests

2. Code snippets en Xcode

Crea snippets reutilizables: arrastra codigo al Code Snippet Library.

3. Debugging con LLDB

po variable    # print object
p variable     # print value

4. Instruments para profiling

  • Time Profiler: detecta cuellos de botella en CPU
  • Allocations: rastrea fugas de memoria
  • Leaks: detecta retain cycles

5. Property Wrappers personalizados

Usalos para validacion, normalizacion o transformacion automatica.

6. Result Builders para DSLs

Crea DSLs con @resultBuilder para construir estructuras complejas.

7. swift-format

brew install swift-format
swift-format format --in-place Sources/

8. Package plugins

SPM soporta plugins (Swift 5.6+) para generar codigo, formatear, lintar.

9. Any vs some vs any (existential)

  • Usa some para ocultar tipos concretos con dispatch estatico
  • Usa any para heterogeneidad con boxed protocol types
  • Usa genericos para maximo rendimiento

10. Codable personalizado con CodingKeys

struct Product: Codable {
    let id: Int
    let name: String
    enum CodingKeys: String, CodingKey {
        case id
        case name = "product_name"
    }
}

11. @MainActor para UI

Marca funciones y tipos con @MainActor para garantizar que el codigo de UI se ejecute en el hilo principal.

12. Swift Testing (Swift 6+)

import Testing

@Test func suma() {
    #expect(2 + 2 == 4)
}

⚠️ Errores comunes

Si algo falla, lo más probable es que esté en esta tabla:

ErrorCausaSolución
Cannot find 'X' in scopeVariable, función o tipo no declarado, o falta importAñade el import del framework/módulo; revisa la ortografía
Value of optional type 'X?' must be unwrappedAccedes a un valor opcional sin desempaquetarUsa if let, guard let, ?? (nil-coalescing) o !.unwrap (solo si estás seguro)
'init(coder:)' has not been implementedUna subclase de UIView / UIViewController no implementa el initializer requeridoAñade required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
Generic parameter 'X' could not be inferredEl compilador no puede deducir el tipo genérico desde los argumentosEspecifica el tipo explícicamente: funciona<T>(valor: T) o pasa el tipo como argumento
No such module 'X' (en Xcode)Falta importar el framework o el paquete SPM/CocoaPods no está integradoVerifica que el paquete está en File > Add Package Dependencies; haz pod install si usas CocoaPods
Expression was too complex to be solved in reasonable timeUna expresión con many anidaciones supera el límite del tipo-checkerDivide la expresión en variables intermedias o extrae funciones auxiliares
Variable with the same name is being used in a different captureCierres (closures) capturan variables con el mismo nombre en scopes distintosRenombra las variables o usa un nombre único en cada closure

🔗 Guías relacionadas

Referencias y documentacion oficial

  1. Swift.org (oficial)
  2. Swift Book
  3. Apple Developer Documentation
  4. Swift by Sundell
  5. Hacking with Swift
  6. NSHipster
  7. raywenderlich.com
  8. Swift Evolution
  9. Apple WWDC Videos
  10. Swift Package Index
  11. Vapor docs
  12. SwiftLint docs
  13. Xcode Instruments Guide
  14. SwiftUI Tutorials
  15. The Swift Programming Language
  16. Swift Algorithms
  17. Swift Collections
  18. Swift Async Algorithms
  19. Kodeco (formerly RW)
  20. Awesome Swift
COMPARTIR:
ETIQUETADO EN:
COMENTARIOS:

📋 Contenido