Swift 6.4: las novedades que más vas a notar en el día a día
Arturo Rivas Arias
🧭 Swift 6.4 no es una versión pensada únicamente para generar grandes titulares. Muchas de sus novedades van en otra dirección: reducir pequeñas fricciones, hacer más explícitas algunas intenciones del código y facilitar migraciones reales en proyectos que ya tienen varios años de vida. Es justo la idea que transmite la sesión What’s new in Swift de la WWDC26: cambios pequeños en apariencia, pero bastante útiles cuando los llevas a una app de verdad.
La propia sesión de Apple lo plantea así: Swift 6.3 y Swift 6.4 traen mejoras de ergonomía cotidiana, concurrencia, librerías, interoperabilidad, rendimiento y para Embedded Swift. No todo será igual de relevante para una aplicación de iOS o macOS tradicional, pero sí hay varias novedades que merece la pena tener en el radar.
Menos ruido con any y some opcionales
Hasta ahora, cuando queríamos expresar un protocolo existencial opcional con any, o un tipo opaco opcional con some, Swift obligaba a usar paréntesis para evitar ambigüedades de precedencia.
let formatter: (any FormatStyle)?
En Swift 6.4 esto se simplifica y el compilador entiende directamente la intención:
let formatter: any FormatStyle?
No cambia la arquitectura de una aplicación, pero sí elimina una rareza sintáctica que aparecía en APIs genéricas, factories o capas de presentación donde exponemos dependencias opcionales.
Ignorar errores en tareas asíncronas: ahora el compilador te avisa
Otra mejora importante afecta a Swift Concurrency. Si una tarea puede arrojar un error y ese error es ignorado, Swift 6.4 te lo advierte con un warning. Es una mejora pequeña, pero ataca un problema muy real: crear una tarea, hacer una llamada falible dentro, olvidarnos su resultado y perder el error por el camino.
Task {
try await syncFavorites()
}
Ese código parece inocente, pero si syncFavorites() falla, el error puede quedar oculto y nadie lo captura. La solución depende de la intención. Podemos manejar el error dentro de la propia tarea:
Task {
do {
try await syncFavorites()
} catch {
logger.error("No se pudieron sincronizar favoritos: \(error)")
}
}
O conservar la tarea para consultar su resultado más tarde:
let syncTask = Task {
try await syncFavorites()
}
try await syncTask.value
defer también puede llamar a funciones async
Swift 6.4 elimina una restricción molesta: ahora se pueden llamar funciones async desde un bloque defer. Esto encaja muy bien con operaciones que deben cerrar, confirmar o revertir algo al salir de una función asíncrona.
func uploadDraft(_ draft: Draft) async throws {
let session = try await UploadSession.start()
defer {
await session.finishIfNeeded()
}
try await session.sendMetadata(draft.metadata)
try await session.sendBody(draft.body)
}
El beneficio no es solo estético. defer vuelve a ser el lugar natural para expresar “esto debe ocurrir al salir”, incluso cuando esa limpieza también necesita suspensión. Cerrar una conexión puede ser un buen ejemplo, y ahora podrás hacerlo incluso si la llamada es asíncrona.
weak let y ~Sendable: intención más precisa
Swift 6.4 también mejora cómo expresamos seguridad de concurrencia. En algunos casos, una clase necesitaba @unchecked Sendable simplemente porque tenía una propiedad weak var. Si esa referencia no debía cambiar después de inicializarse, ahora puede modelarse como weak let.
final class ImagePipeline: Sendable {
weak let delegate: ImagePipelineDelegate?
init(delegate: ImagePipelineDelegate?) {
self.delegate = delegate
}
}
También aparece la sintaxis ~Sendable, útil para decir explícitamente que un tipo no debe considerarse Sendable.
class MutableDraftCache: ~Sendable {
private var drafts: [Draft.ID: Draft] = [:]
}
Esto evita depender de inferencias implícitas cuando el diseño del tipo está ligado a un único hilo, a un actor concreto o a un estado mutable que no queremos cruzar entre dominios de concurrencia.
anyAppleOS: comprobación de disponibilidad menos repetitiva
La lista de sistemas operativos detro del ecosistema Apple se ha vuelto cada vez más larga: iOS, macOS, watchOS, tvOS, visionOS… Swift 6.4 introduce anyAppleOS para expresar una disponibilidad común en todas las plataformas Apple.
@available(anyAppleOS 27.0, *)
func configureGlassToolbar() {
// Código común para los sistemas Apple modernos
}
Y si una plataforma necesita una excepción, se puede combinar con atributos más específicos:
@available(anyAppleOS 27.0, *)
@available(macOS, introduced: 27.0)
func enableAdvancedWindowControls() {
// Ajustes específicos de escritorio
}
Para código compartido entre iOS, macOS y visionOS, esto reduce bastante el ruido visual.
@diagnose: controlar los warnings de forma exhaustiva
@diagnose permite modificar el comportamiento de ciertos diagnósticos dentro de una declaración concreta. Por ejemplo, puede ser útil durante una migración donde todavía usamos una API deprecada de forma consciente, pero no queremos silenciar el aviso en todo el proyecto.
@diagnose(.deprecated, severity: .ignore)
func loadLegacyProfile() async throws -> Profile {
try await OldProfileClient().fetchCurrentUser()
}
La idea no es esconder deuda técnica, sino aislarla. También se puede usar en sentido contrario: convertir ciertos avisos en errores para las zonas especialmente delicadas.
@diagnose(.strictMemorySafety, severity: .error)
func parseSignedPayload(_ bytes: UnsafeRawBufferPointer) throws -> Payload {
try PayloadParser.parse(bytes)
}
En proyectos grandes, esto da un control mucho más fino que activar o desactivar warnings a nivel global.
Selectores de módulo con ::
Swift 6.3 introduce los selectores de módulo con ::, una sintaxis pensada para resolver conflictos cuando dos módulos exponen símbolos con el mismo nombre.
import SwiftUI
import DesignSystem
let nativeView = SwiftUI::View.self
let componentView = DesignSystem::View.self
El ejemplo realista no es diseñar APIs con nombres conflictivos a propósito, sino como resolución de conflictos que vienen de dependencias externas, módulos generados a posteriori o macros. Con ::, el identificador de la izquierda se interpreta siempre como módulo, no como tipo.
Librerías: cancelación, diccionarios, rutas y testing
La biblioteca estándar también gana herramientas interesantes. Una de ellas es el task cancellation shield, pensado para regiones de aislamiento donde no queremos que una cancelación interrumpa una operación crítica, como terminar de escribir un fichero y que pueda quedar un estado corrupto.
try await Task.withCancellationShield {
try await cacheFile.write(contents)
}
La clave está en mantener ese bloque con un tamaño contenido. No es una vía para ignorar cancelaciones, sino para cerrar correctamente una operación ya iniciada.
También llega mapKeyedValues, una variante de mapValues donde la transformación recibe tanto la clave como el valor.
let unreadByFolder = messagesByFolder.mapKeyedValues { folder, messages in
"\(folder.name): \(messages.filter(\.isUnread).count)"
}
Swift Testing mejora con issues de severidad configurable, cancelación dinámica de tests y mejoras de interoperabilidad con XCTest. Para migraciones graduales es importante: las aserciones de XCTest pueden reportarse como issues dentro de Swift Testing, y APIs como #expect pueden usarse desde un XCTestCase.
@Test(arguments: supportedLocales)
func formattedDateUsesExpectedLocale(locale: Locale) async throws {
guard locale.identifier != "legacy_POSIX" else {
Test.cancel("Locale legacy no soportada por este formatter")
}
#expect(formatDate(locale: locale).isEmpty == false)
}
Interoperabilidad y rendimiento
Swift 6.4 también tiene mejoras notables fuera del caso clásico de aplicaciones para sistemas de Apple. Una de ellas es el operador @C, que permite exponer funciones Swift a C usando tipos compatibles, mejoras en Swift-Java, SDK oficial de Swift para Android, soporte en editores como VSCode, avances en WebAssembly y mejoras en Swift embebido.
Para la mayoría de apps iOS, quizá esto no cambie el código de mañana. Pero sí refuerza una dirección clara: Swift quiere ser útil más allá de UIKit, SwiftUI o Foundation.
En rendimiento, aparecen herramientas más explícitas como @inline(always) y @specialized. No son atributos para usar por defecto, pero pueden tener sentido en rutas críticas o para medidas con Instruments.
@inline(always)
func clamp(_ value: Double, min lower: Double, max upper: Double) -> Double {
Swift.max(lower, Swift.min(value, upper))
}
La regla sigue siendo la misma: primero medir, después optimizar. Estos atributos no sustituyen al optimizador, solo permiten intervenir cuando sabemos que una decisión concreta importa.
Una versión de Swift menos espectacular, pero más cómoda
Swift 6.4 no destaca por una única novedad enorme, sino por muchas piezas pequeñas que eliminan fricción: menos paréntesis, mejores warnings, disponibilidad más compacta, diagnósticos más controlados, interoperabilidad más amplia y librerías más capaces.
Para quienes desarrollamos apps Apple, lo más interesante está en esa suma. Swift sigue avanzando hacia un lenguaje más expresivo, más seguro y más útil en proyectos reales, sin obligarnos a reescribirlo todo cada año.