Al ejecutar este código en Objective-C, el resultado es bastante inesperado.
NSArray *array = [NSArray arrayWithObjects:@1, @2, nil];
NSLog(@"%@", [array class]);
// salida: __NSArrayI
Aunque creamos un NSArray, el nombre de la clase es __NSArrayI. Para un array vacío puede aparecer __NSArray0, y con un solo elemento, __NSSingleObjectArrayI.
No es un error. Es el patrón de clúster de clases (Class Cluster).
NSArray, NSString, NSNumber y NSDictionary son todas clases abstractas.
Las instancias reales se crean como subclases ocultas según la situación.
Es el tema del punto 9 de Effective Objective-C 2.0 y resulta inevitable mientras uses Foundation.
Resumen de lo esencial (3 puntos)
- Las colecciones de Foundation, como NSArray, son clases abstractas que solo exponen la interfaz pública.
- El inicializador elige y devuelve una instancia de una subclase privada (una variante del patrón de fábrica).
- Por eso, la
[array class]comparación es peligrosa y la subclasificación tiene reglas estrictas.
¿Por qué se diseñó así?
Incluso un array puede requerir varias estrategias de optimización.
- Array inmutable de 0 elementos: basta un singleton, sin crearlo cada vez
- De 1 a 2 elementos: insertar los punteros en la estructura es más rápido
- Array mutable grande: necesita una implementación separada con estrategia de realocación
En lugar de concentrarlo todo con if en una implementación de NSArray, Foundation prepara subclases específicas para cada caso y el método de fábrica devuelve la adecuada. El código cliente solo conoce la interfaz de NSArray, mientras la implementación interna puede cambiar libremente según la versión del sistema operativo.
En realidad, [NSArray alloc] devuelve un objeto temporal llamado __NSPlaceholderArray, que se convierte en la implementación real durante las llamadas posteriores de la familia init. Aunque solemos encadenar alloc e init por costumbre, en un clúster de clases esos pasos recorren objetos distintos.
Dos trampas habituales en producción
Trampa 1: comparar clases directamente
// Código peligroso
if ([object class] == [NSArray class]) {
// Nunca entra aquí, porque las clases reales son __NSArrayI , entre otras.
}
// Código correcto
if ([object isKindOfClass:[NSArray class]]) {
// Comprueba todo el clúster
}
Los miembros del clúster son subclases de la clase pública; hay que comprobar la familia con isKindOfClass:, no comparar la igualdad.
Trampa 2: subclasificar a la ligera
Parece suficiente heredar de NSArray y sobrescribir un método, pero esa subclase ni siquiera hereda almacenamiento: una clase abstracta no lo tiene. Para integrarla correctamente en el clúster hay que seguir ciertas reglas.
- Proporcionar almacenamiento propio (normalmente, conservar dentro un NSArray real)
- Implementar todos los métodos primitivos requeridos (count y objectAtIndex: en NSArray)
Los demás métodos funcionan sobre esos primitivas, así que basta implementar dos para habilitar toda la funcionalidad del clúster. Aun así, Effective Objective-C es claro: en la mayoría de los casos, la composición (una clase contenedora con un NSArray) es mejor que la herencia.
Por qué también importa a los desarrolladores de Swift
Array en Swift es una estructura, así que parece ajeno a este patrón, pero hay dos puntos de contacto.
El primero es el puenteo. Cuando un NSArray procedente de una API de Objective-C se convierte en Swift Array, detrás siguen existiendo implementaciones reales como _ContiguousArrayStorage o __NSArrayI. Al investigar problemas de rendimiento del puenteo, acabarás encontrándolas.
El segundo es que la idea llegó a Swift. «El tipo público expone solo la interfaz y un tipo oculto se encarga de la implementación» se refleja en wrappers de borrado de tipo como AnySequence y en la palabra clave some (tipos opacos). Hay un único tipo de retorno, pero solo el compilador y la implementación conocen el tipo real: la misma línea conceptual que los clústeres de clases.
Resumen
- NSArray, NSString, NSNumber y NSDictionary son clases abstractas; las instancias reales son subclases ocultas
- Para comprobar tipos, usa siempre isKindOfClass:; no compares la igualdad de clases
- La subclasificación del clúster exige implementar los métodos primitivos; normalmente conviene la composición
- Separar interfaz e implementación es un diseño clásico que llega hasta los tipos opacos de Swift
Si encuentras en el depurador una clase con el prefijo desconocido __NS, ya no tienes por qué alarmarte. Solo estás viendo la optimización que Foundation ha perfeccionado durante casi 30 años.

