Si en la entrega sobre dispatch vimos «cómo se traduce una llamada», ahora veremos «cómo se coloca un valor».
Este contenido continúa el artículo anterior Swift avanzado #5.
¿El tamaño de un struct es la suma de los tamaños de sus propiedades? No.
¿Creería que cambiar el orden de declaración de las propiedades puede cambiar el tamaño de un struct? Esta es la entrega sobre layout de memoria de la serie avanzada.
Este conocimiento resulta especialmente útil al diseñar modelos para almacenar cientos de miles de elementos en un array o cuando el gráfico de memoria de Instruments es más grande de lo esperado.
También responde a la pregunta pendiente de la entrega sobre some·any: «¿cuánto mide realmente esa caja?».
Tres medidas: size, stride y alignment
Swift proporciona herramientas estándar para consultar las medidas de memoria de un tipo. Es MemoryLayout.
MemoryLayout<Int>.size // 8
MemoryLayout<Bool>.size // 1
struct Point { var x: Double; var y: Double }
MemoryLayout<Point>.size // 16
Son tres medidas, y cada una tiene un significado distinto.
size es la huella de memoria contigua de un valor. No incluye el tail padding necesario para la alineación al final del tipo.
alignment (alineación) define las reglas de las direcciones en las que puede colocarse el tipo. La CPU lee eficazmente un valor de 8 bytes desde una dirección múltiplo de 8, y según la arquitectura puede ser obligatorio.
Por eso cada tipo tiene la restricción de «colocarse en una dirección múltiplo de n». Las cifras siguientes corresponden a las plataformas Apple actuales de 64 bits: la alignment de Double es 8 y la de Bool, 1.
stride (zancada) es la distancia hasta el siguiente elemento de un array. Como las restricciones de alineación pueden dejar espacio vacío (padding) entre elementos, stride ≥ size.
Este es el ejemplo clásico que muestra la relación entre las tres medidas.
struct Bad {
var flag: Bool // 1bytes
var value: Double // 8bytes; debe colocarse en una dirección múltiplo de — 8
var count: Int32 // 4bytes
}
MemoryLayout<Bad>.stride // 24
struct Good {
var value: Double // 8
var count: Int32 // 4
var flag: Bool // 1
}
MemoryLayout<Good>.stride // 16
El contenido es el mismo, pero cambiar el orden ahorra un 33 % en un array. Bad necesita 7 bytes de padding para alinear el value después del flag (Swift ABI Type Layout).
En la práctica, agrupar primero las propiedades con mayor alignment es una heurística habitual para reducir el padding. Sin embargo, no garantiza el óptimo para todos los tipos; el resultado final debe medirse con MemoryLayout.
Actualmente, el algoritmo de layout de fragile struct de Swift coloca las propiedades almacenadas en el orden de declaración. Pero el orden de los campos no está garantizado por el lenguaje ni por la ABI (Swift ABI · SE-0260). El código que necesita offsets exactos no debe depender de valores observados.
Segundo, esta optimización solo importa cuando hay muchas instancias. Frente a los 8 bytes de un objeto de configuración, 8 bytes por elemento en un array de un millón suman 8 MB (Swift ABI Type Layout).
Como siempre, primero hay que medir.
La magia de enum: cuando la etiqueta de Optional sale gratis
El layout de enum muestra claramente la inteligencia del compilador de Swift. Debe almacenar un discriminador de caso (etiqueta), pero lo oculta en los patrones de bits no utilizados (extra inhabitants) del tipo de payload.
El caso representativo es Bool?. En las plataformas Apple actuales de 64 bits, MemoryLayout<Bool?>.size es 1.
Bool solo usa dos patrones de su byte (0 y 1), así que uno de los patrones restantes representa nil.
Lo mismo ocurre con los opcionales de tipos por referencia. Los punteros de clase de Swift no usan null como objeto, así que nil se representa con 0. En 64 bits, el size de AnyObject? es 8.
Pero no todos los Optional salen gratis. Int utiliza patrones de bits de 8 bytes para representar valores. Por eso, en el entorno actual, el size de Int? es 9 y su stride es 16 (Swift ABI).
La expresión de la entrega sobre opcionales, «la seguridad cuesta casi nada», es precisa para los tipos que pueden reutilizar representaciones adicionales. La sintaxis Optional no garantiza coste de memoria cero para todos los tipos.
Medición de la caja: un existential de protocolo único ocupa 5 words
En la entrega sobre some·any solo dijimos que «any es una caja y tiene un coste»; ahora podemos medirlo.
protocol Shape {}
MemoryLayout<any Shape>.size // 40 — actualmente, entorno de 64 bits
Un único protocolo sin restricción de clase, any Shape, ocupa 40 bytes, es decir, 5 words, en el entorno actual de 64 bits. Consta de un búfer de valores de 3 words, un puntero a los metadatos de tipo y un puntero a la witness table.
Aquí aparece una bifurcación importante. Si el size y el alignment del valor caben en las condiciones del búfer inline de 3 words, el valor se almacena directamente en él.
Si supera las condiciones, el valor se asigna en un espacio separado y el búfer contiene un puntero. Esto puede añadir costes de asignación del heap, conteo de referencias y acceso indirecto (Swift ABI · SE-0335).
La cifra de 5 words no es el tamaño fijo de todos los tipos any. Una combinación de protocolos puede añadir punteros a witness tables.
Un existential de protocolo exclusivo para clases utiliza un layout más pequeño, sin búfer de valores ni puntero de metadatos separado.
Si el código orientado a protocolos resulta inesperadamente lento, puede evaluarse eliminar la caja mediante some·genéricos o reducir el tipo almacenado. El efecto real debe comprobarse con Instruments y benchmarks (SE-0335 Existential any).
También conviene revisar las medidas de las instancias de clase. En las plataformas Apple actuales de 64 bits, el stride de un valor de tipo clase es 8: es el tamaño de la referencia, no del objeto.
Los objetos nativos del heap de Swift suelen tener un header compuesto por un puntero a metadatos y datos de conteo de referencias inline; en 64 bits ocupa 16 bytes. El tamaño real de la asignación puede ser mayor por la alineación de las propiedades y el redondeo del allocator (Swift HeapObject · Swift RefCount).
El coste oculto de los tipos por referencia mencionado en la entrega sobre ARC incluye este header y las operaciones de asignación, liberación y conteo. Si se crean cientos de miles de pequeños fragmentos de datos como clases, la gestión puede superar al propio dato.
Caja de herramientas práctica: volver a medir, comprobar y cuestionar
Recapitulemos las herramientas prácticas para trabajar en este nivel.
Validar hipótesis con MemoryLayout. Al diseñar structs de modelos que se producen masivamente, basta con adquirir el hábito de medir el stride una vez.
También se puede incluir un presupuesto en las pruebas unitarias, como #expect(MemoryLayout<Tick>.stride <= 32). Si más adelante se añade una propiedad y se supera el presupuesto, se detectará de inmediato.
Comprobar el heap con Instruments Allocations. Si «el código está basado principalmente en structs, pero hay muchas asignaciones en el heap», los sospechosos están claros.
La caja any, las capturas de cierres (entrega sobre cierres), el almacenamiento CoW (copy-on-write) y las instancias de clase. El desglose por tipo de Allocations indica cuál es el responsable.
La ilusión de las colecciones copy-on-write: en las plataformas Apple actuales de 64 bits, MemoryLayout<[Int]>.stride es 8. Un valor Array es un struct pequeño que referencia indirectamente el almacenamiento del heap, pero no debe asumirse esta cifra como ABI fija.
Por eso no se debe concluir que un struct que contiene un array es «ligero» solo porque su stride sea pequeño.
El peso real está en el almacenamiento del heap, y Allocations lo muestra. El principio CoW es el descrito en el artículo independiente.
Por último, mantengamos la perspectiva. El conocimiento de esta entrega no se utiliza a diario.
Pero el valor de saber «por qué» aparece cuando surge un problema. Cuando el gráfico de memoria resulta extraño, se puede partir de una lista de sospechosos en lugar de especular; esa es la ventaja de quien ha descendido una vez a este nivel.
Resumen
- Las medidas de un tipo son tres. size es la huella sin tail padding, alignment es la regla de alineación de direcciones y stride es el intervalo entre elementos contiguos. En structs producidos masivamente, agrupar propiedades con mayor alignment suele reducir el padding, pero siempre hay que medir.
- enum puede ocultar la etiqueta en los extra inhabitants del payload. Bool? y los opcionales de referencias no necesitan espacio adicional, pero tipos como Int? necesitan espacio separado para la etiqueta.
- Un existential de protocolo único sin clase ocupa 5 words en 64 bits. Si el size o el alignment del valor supera las condiciones del búfer de 3 words, pueden aparecer almacenamiento separado y costes indirectos.
- El header básico de los objetos nativos de Swift en 64 bits ocupa normalmente 16 bytes. El tamaño real de la asignación puede ser mayor según las propiedades y la alineación del allocator.
- Las herramientas son MemoryLayout (validación del diseño) e Instruments Allocations (medición real). Optimizar el layout sin medir es optimización prematura.
La próxima entrega trata la magia del tiempo de compilación: Swift macros, el mecanismo que escribe código detrás de #Preview y @Observable.
Fuentes y criterios de verificación
- MemoryLayout — Apple Developer Documentation · verificado el 2026-08-19 · fundamento: definiciones de API de size, alignment y stride
- Swift ABI Type Layout — Proyecto Swift · documentación oficial · verificado el 2026-08-19 · fundamento: layout de struct, enum y existential container
- SE-0260 Library Evolution — Swift Evolution · verificado el 2026-08-19 · fundamento: alcance de las garantías del lenguaje y la ABI sobre el orden de los campos de struct
- SE-0335 Existential any — Swift Evolution · verificado el 2026-08-19 · fundamento: búfer inline de 3 words del existential y coste de memoria dinámica
- Swift HeapObject · Swift RefCount — Runtime de Swift · verificado el 2026-08-19 · fundamento: header de objetos nativos del heap y conteo de referencias inline

![Imagen de portada de [Swift avanzado #6] Layout de memoria de Swift: el orden cambia el tamaño](/assets/images/posts/dfd50aa0-6496-4f00-a59f-21b04c18ed87/swift-memory-layout-size-stride-alignment.jpg)