Swift es el lenguaje de programación de Apple que combina el poder de Objective-C con la seguridad de los lenguajes modernos. A diferencia de muchos otros lenguajes, Swift ofrece construcciones únicas: value types con copia automática, Actores con aislamiento a nivel de compilador, Property Wrappers para reutilizar lógica de propiedades y Programación Orientada a Protocolos como alternativa a la herencia clásica. Según la Documentación de Apple Swift, estas características hacen que el código sea más seguro y eficiente.
Puntos Clave
En Swift, struct es un tipo valor, mientras que class es un tipo referencia. Esta diferencia fundamental determina el comportamiento durante la asignación, el paso a funciones y la gestión de memoria. Struct se copia completamente en cada asignación, class se pasa por referencia — solo se copia el puntero al objeto en el heap.
Struct implementa automáticamente la semántica de valor — cada instancia es independiente. La mutación de una propiedad de struct solo es posible a través de var, y los métodos que modifican propiedades deben marcarse como mutating. Struct no soporta herencia pero puede conformarse a protocolos. La biblioteca estándar de Swift usa struct para String, Array, Dictionary, Int, Bool — todos los tipos fundamentales son tipos valor.
struct User {
let id: Int
var name: String
mutating func updateName(_ newName: String) {
name = newName
}
}
var user1 = User(id: 1, name: "Alice")
var user2 = user1
user2.updateName("Bob")
// user1.name === "Alice", user2.name === "Bob"
// Copia completa — instancias independientes
Class se almacena en el heap y soporta igualdad por referencia. Varias variables pueden referenciar al mismo objeto, los cambios a través de una referencia son visibles en todas las demás. Class soporta herencia, permite sobrescritura de métodos (override), tiene deinit para limpieza de recursos. En Swift, class se usa con menos frecuencia que struct — Apple recomienda comenzar con struct y cambiar a class solo cuando se necesite herencia o semántica de referencia.
class Car {
let model: String
var speed: Int
init(model: String, speed: Int) {
self.model = model
self.speed = speed
}
func accelerate(_ amount: Int) {
speed += amount
}
}
let car1 = Car(model: "Tesla", speed: 0)
let car2 = car1
car2.accelerate(50)
// car1.speed === 50, car2.speed === 50
// Misma referencia — cambios visibles en todas partes
La elección entre struct y class depende de la semántica de los datos. Para configuraciones, modelos de datos y estados usa struct — la copia es más segura y predecible. Para objetos con identidad (sesión de usuario, conexión a base de datos) y cuando se necesita herencia, usa class.
Actor es un tipo referencia que protege su estado mutable del acceso concurrente. El compilador de Swift garantiza que las variables de actor solo pueden accederse desde el mismo contexto del actor — cualquier acceso externo causa un error de compilación. Esto previene condiciones de carrera a nivel de lenguaje, sin bloqueos manuales.
Todas las propiedades y métodos de actor están aislados por defecto. Llamar a un método de actor desde fuera requiere await — Swift cambia la ejecución al actor y viceversa. Actor sincroniza el acceso secuencialmente: las solicitudes de diferentes tareas se ponen en cola. Esto garantiza un estado consistente dentro del actor. Los métodos no aislados (nonisolated) pueden llamarse sin await pero no pueden acceder a propiedades mutables.
actor BankAccount {
private var balance: Double
init(initialBalance: Double) {
balance = initialBalance
}
func deposit(amount: Double) {
balance += amount
}
func withdraw(amount: Double) throws {
guard balance >= amount else {
throw BankError.insufficientFunds
}
balance -= amount
}
nonisolated func accountInfo() -> String {
return "Cuenta Bancaria"
}
}
let account = BankAccount(initialBalance: 1000)
await account.deposit(amount: 500)
// balance = 1500 — atómico, sin condición de carrera
Actor es útil para gestores de estado, cachés y servicios de red donde múltiples tareas leen y escriben datos concurrentemente. A diferencia de la sincronización manual mediante NSLock o DispatchQueue, actor se verifica en tiempo de compilación — es imposible cometer un error. Los errores de aislamiento aparecen en tiempo de compilación, no en tiempo de ejecución.
Property Wrapper es una anotación que añade comportamiento a una propiedad sin cambiar su declaración. SwiftUI y Combine proporcionan wrappers integrados para gestionar el estado de las vistas. El compilador genera código que envuelve el acceso a la propiedad, añadiendo lógica de observación, sincronización o transformación.
@Published se usa en clases que conforman ObservableObject para publicar cambios automáticamente. Los suscriptores de Combine reciben notificaciones en cada actualización. @State es el estado local de una vista en SwiftUI. SwiftUI redibuja automáticamente la vista cuando @State cambia. @Binding crea una conexión bidireccional entre una vista padre e hija sin poseer los datos.
import SwiftUI
import Combine
@main
struct CounterApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
class CounterViewModel: ObservableObject {
@Published var count: Int = 0
func increment() {
count += 1
}
}
struct ContentView: View {
@State private var greeting: String = "Hola"
@StateObject private var viewModel = CounterViewModel()
var body: some View {
VStack {
CounterDisplay(count: $viewModel.count)
Text(greeting)
Button("Incrementar", action: viewModel.increment)
}
}
}
struct CounterDisplay: View {
@Binding var count: Int
var body: some View {
Text("Conteo: \(count)")
}
}
Los Property Wrappers personalizados se crean mediante @propertyWrapper y una estructura con un campo wrappedValue. Esto permite reutilizar lógica: validación, normalización, almacenamiento en UserDefaults. Los Property Wrappers reducen significativamente el código repetitivo y hacen explícita la intención del desarrollador mediante anotación.
@propertyWrapper
struct Clamped<T: Comparable> {
private var value: T
let range: ClosedRange<T>
init(wrappedValue: T, range: ClosedRange<T>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
var wrappedValue: T {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
}
struct Settings {
@Clamped(range: 0...100) var volume: Int = 50
}
var s = Settings()
s.volume = 150
// s.volume === 100 — valor automáticamente limitado
Generics es un potente mecanismo de Swift para escribir código flexible sin perder seguridad de tipos. Las funciones y tipos genéricos funcionan con cualquier tipo que cumpla las restricciones. La Programación Orientada a Protocolos (POP) es un paradigma de Swift donde protocol + extension reemplazan la herencia clásica de clases, proporcionando composición de comportamiento.
Swift permite restringir parámetros genéricos con protocolos (cláusulas where). Protocolo con tipo asociado es el equivalente genérico para protocolos: el tipo concreto se determina en el momento de la conformidad. Esta es la base para colecciones y algoritmos type-safe. La biblioteca estándar de Swift usa activamente generics: Array, Dictionary, Optional — todos son tipos genéricos.
protocol Cacheable {
associatedtype Key: Hashable
associatedtype Value
func get(_ key: Key) -> Value?
mutating func set(_ value: Value, for key: Key)
}
struct MemoryCache<K: Hashable, V>: Cacheable {
private var storage: [K: V] = [:]
typealias Key = K
typealias Value = V
func get(_ key: K) -> V? {
return storage[key]
}
mutating func set(_ value: V, for key: K) {
storage[key] = value
}
}
func firstMatch<T: Equatable>(in array: [T], target: T) -> Int? {
for (index, item) in array.enumerated() {
if item == target {
return index
}
}
return nil
}
let numbers = [10, 20, 30, 40]
let index = firstMatch(in: numbers, target: 30)
// index === 2 — un método genérico para todos los tipos Equatable
La extension de protocolo es un mecanismo clave de POP. En una extension se puede proporcionar una implementación por defecto de los métodos del protocolo. Los tipos que se conforman al protocolo obtienen esta implementación gratuitamente y pueden sobrescribirla si es necesario. Esto permite construir jerarquías de comportamiento sin herencia de clases — una alternativa a la herencia múltiple, que no existe en Swift.
protocol Drivable {
var speed: Double { get set }
func drive()
}
extension Drivable {
func drive() {
if speed > 0 {
print("Conduciendo a \(speed) km/h")
}
}
}
struct Bicycle: Drivable {
var speed: Double = 15
}
struct Plane: Drivable {
var speed: Double = 900
func drive() {
print("Volando a \(speed) km/h")
}
}
let bike = Bicycle()
bike.drive() // "Conduciendo a 15.0 km/h" — por defecto
let plane = Plane()
plane.drive() // "Volando a 900.0 km/h" — sobrescrito
POP con extension permite composición a través de múltiples protocolos — un tipo puede conformarse a varios protocolos, obteniendo sus implementaciones. Esto es más flexible que la herencia única de clases, donde toda la jerarquía está fija. La biblioteca estándar de Swift está construida sobre POP: CustomStringConvertible, Equatable, Hashable, Codable — todos son protocolos con extensiones.
Enum en Swift es una construcción significativamente más potente que en otros lenguajes. Cada caso puede tener valores asociados — un conjunto arbitrario de datos de cualquier tipo. Combinado con extension y pattern matching, enum se convierte en la base para modelar estados, resultados de operaciones y máquinas de estados finitos.
Los valores asociados permiten que cada caso almacene datos únicos. Por ejemplo, el caso loading no contiene datos, success contiene el resultado, failure contiene un error. El pattern matching mediante switch extrae los valores asociados y maneja cada caso. El compilador verifica la exhaustividad (exhaustive switch) — esto elimina estados olvidados.
enum NetworkResult<T> {
case loading
case success(T)
case failure(Error)
}
enum MediaFile {
case image(url: URL, width: Int, height: Int)
case video(url: URL, duration: Double)
case audio(url: URL, bitrate: Int)
}
func handle(_ result: NetworkResult<String>) {
switch result {
case .loading:
print("Cargando...")
case .success(let data):
print("Recibido: \(data)")
case .failure(let error):
print("Error: \(error.localizedDescription)")
}
}
let file = MediaFile.video(url: URL(string: "https://example.com/video.mp4")!, duration: 120)
if case .video(let url, let duration) = file {
print("Video \(url) duración \(duration)s")
}
Swift permite añadir métodos, propiedades computadas y conformidad a protocolos mediante extension para enum. Esto no requiere cambiar la declaración original — el código se mantiene limpio y modular. Los métodos en extension pueden trabajar con valores asociados, calcular datos derivados e implementar lógica de negocio.
extension MediaFile {
var fileName: String {
switch self {
case .image(let url, _, _),
.video(let url, _),
.audio(let url, _):
return url.lastPathComponent
}
}
var description: String {
switch self {
case .image(_, let w, let h):
return "Imagen \(w)x\(h)"
case .video(_, let d):
return "Video \(d)s"
case .audio(_, let b):
return "Audio \(b) kbps"
}
}
}
extension MediaFile: Equatable {
static func == (lhs: MediaFile, rhs: MediaFile) -> Bool {
return lhs.fileName == rhs.fileName
}
}
let files: [MediaFile] = [file]
files.forEach { print($0.description) }
// "Video 120.0s"
Enum con valores asociados + extension es el reemplazo de las clases selladas de Kotlin o las uniones discriminadas de otros lenguajes. Swift usa esta combinación para Result (un tipo estándar en la biblioteca estándar), Optional (Optional es un enum con .none y .some) y el manejo de estados en SwiftUI. El pattern matching garantiza que todos los estados se manejan — los errores en tiempo de ejecución son imposibles.
Preguntas Frecuentes
Struct — tipo valor, se copia al asignarse, no soporta herencia. Class — tipo referencia, se pasa por referencia, soporta herencia, deinit y verificación de identidad (===). Apple recomienda comenzar con struct y cambiar a class solo cuando se necesite semántica de referencia.
Actor — tipo referencia thread-safe. El compilador impide el acceso concurrente al estado mutable desde diferentes contextos. Usa Actor para gestores de estado, cachés y servicios a los que acceden múltiples tareas. Llamar a métodos de actor requiere await.
Principales: @State — estado local de la vista; @Binding — conexión con datos sin posesión; @Published — publicación de cambios en ObservableObject; @StateObject — posesión de ObservableObject; @EnvironmentObject — dependencia del entorno. Los wrappers personalizados se crean mediante @propertyWrapper.
POP — un paradigma donde protocol + extension definen el comportamiento en lugar de la herencia de clases. Un tipo se conforma a múltiples protocolos, obteniendo sus implementaciones gratuitamente. Es composición sobre herencia — más flexible y seguro. La biblioteca estándar de Swift (Equatable, Codable) está construida sobre POP.
Enum en Swift soporta valores asociados — cada caso puede almacenar datos arbitrarios de diferentes tipos. Combinado con pattern matching (switch), proporciona un modelo potente para estados, errores y Domain-Driven Design. Optional y Result son enum estándar de Swift con valores asociados.
Resumen
Desarrollaremos una aplicación móvil llave en mano
IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.