Objective-c Internals para NSArray y NSMutableArray

Pregunta muy interesante para todos aquellos en Objective-c internals …

Entonces … el NSObject devuelve la misma implementación de copy para ambos y object y una class (como lo esperaba). Sin embargo, NSArray y NSMutableArray devuelven no solo diferentes implementaciones de objectAtIndex: para un object y class, pero cada object tiene una implementación diferente.

¿Alguien sabe por qué el código de seguimiento produce tal comportamiento? … (Al less las implementaciones de class para NSArray y NSMutableArray son iguales :))

 NSObject *obj = [[[NSObject alloc] init] autorelease]; NSLog(@"NSObject instance %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod(object_getClass(obj), @selector(copy)))]); NSLog(@"NSObject class %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod([NSObject class], @selector(copy)))]); NSArray *array = [[[NSArray alloc] init] autorelease]; NSLog(@"NSArray instance %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod(object_getClass(array), @selector(objectAtIndex:)))]); NSLog(@"NSArray class %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod([NSArray class], @selector(objectAtIndex:)))]); NSMutableArray *array1 = [[[NSMutableArray alloc] init] autorelease]; NSLog(@"NSMutableArray instance %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod(object_getClass(array1), @selector(objectAtIndex:)))]); NSLog(@"NSMutableArray class %@", [NSValue valueWithPointer:method_getImplementation(class_getInstanceMethod([NSMutableArray class], @selector(objectAtIndex:)))]); 

El logging

 2012-11-06 16:35:22.918 otest[71367:303] NSObject instance <c0fa7200> 2012-11-06 16:35:23.757 otest[71367:303] NSObject class <c0fa7200> 2012-11-06 16:35:30.348 otest[71367:303] NSArray instance <809a9b00> 2012-11-06 16:35:31.121 otest[71367:303] NSArray class <70bfa700> 2012-11-06 16:35:33.854 otest[71367:303] NSMutableArray instance <f05f9a00> 2012-11-06 16:35:34.824 otest[71367:303] NSMutableArray class <70bfa700> 

Detalles de implementación, todos ellos. Por lo tanto, esta es una pequeña conjetura relativamente bien informada .

Primero, no hay necesidad de saltar a través de tales aros para imprimir un valor hexadecimal, simplemente haz lo siguiente:

 NSLog(@"imp: %p", [NSObject instanceMethodForSelector:@selector(...)]); 

Tenga en count que invocar methodForSelector: en un object de class devuelve el IMP para un método de class.

Ahora, a la pregunta en cuestión.

Una cosa que diferencia Objective-C de otros lenguajes OO populares (pero no todos) es que los objects Class son en realidad instancias de un Metaclass. Ese Metaclass, que realmente no tiene un nombre más allá de "metaclass", responde al protocolo NSObject (más o less). De hecho, es algo así como un derivado de NSObject .

Por lo tanto, cuando obtiene la implementación para ciertos selectores en NSObject para instancias y classs, a menudo serán los mismos. Tenga en count que esto es lo que permite que una Clase sea una key en un NSDictionary; Las classs se pueden copyr. El método de copy NSObject simplemente return [self retain]; y, por supuesto, retain en una class es un no-op ya que están [casi siempre] comstackdos estáticamente en el binary como singletons. Bueno, técnicamente, copy llamadas copyWithZone: eso return [self retain]; (es por eso que tiene que copyWithZone: aunque las zonas están en desuso).

Ahora, como Hot Licks señaló, NS*Array es un clúster de class cuyos detalles de implementación interna han cambiado en los últimos lanzamientos. Solía ​​ser que todas las instancias se NSCFArray a NSCFArray que se configuraba de manera diferente. En versiones más recientes, verá (sic.) __NSArrayI y __NSArrayM instancias que corresponden a instancias inmutables y mutables. Sobre todo, hay más cosas que hacer eso, pero eso es bastante típico.

Que los methods de instancia para objectAtIndex: son diferentes entre las dos classs es típico de los clústeres de class. El punto de un clúster es proporcionar una interfaz primitiva con un montón de methods implementados en términos de esa interfaz primitiva (razón por la cual los encabezados se dividen entre el núcleo @interface y una serie de @interfaces categóricas, las categorías en las classs base se implementan por completo en términos de la API central).

Internamente, hay subclasss concretas de las classs declaradas públicamente dentro del cluster. Esas subclasss concretas están altamente optimizadas para una tarea particular: almacenamiento inmutable frente a almacenamiento mutable, en este caso (pero puede haber muchas más subclasss no públicas optimizadas para diferentes propósitos), donde las subclasss anulan la API anunciada para proporcionar una gran versiones optimizadas de los diferentes methods.

Por lo tanto, lo que está viendo a través de las dos classs son diferentes implementaciones de objectAtIndex: optimizado para el caso mutable vs. el caso inmutable.

Ahora, ¿por qué los methods de class son iguales? Buena pregunta. Intentemos llamarlo:

 ((void(*)(id,SEL,int))[[NSArray class] methodForSelector: @selector(objectAtIndex:)])([NSArray class], @selector(objectAtIndex:), 0); 2012-11-06 09:18:23.842 asdfasdf[17773:303] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '+[NSArray objectAtIndex:]: unrecognized selector sent to class 0x7fff7563b1d0' 

¡Ajá! Por lo tanto, está solicitando la implementación de un método que, cuando se invoca, genera una exception de "no reconoce este método". De hecho:

 NSLog(@"%@", [NSArray class] respondsToSelector:@selector(objectAtIndex:)] ? @"YES" : @"NO"); 2012-11-06 09:24:31.698 asdfasdf[17839:303] NO 

Parece que el time de ejecución está devolviendo un IMP que generará un buen error indicando que intentó, a través de la rotonda, usar un selector en el que el object seleccionado (una instancia de metaclass, en este caso) no responde. Es por eso que la documentation para instanceMethodForSelector: indica que debe usar respondsToSelector: antes en casos donde pueda haber alguna duda sobre si el destino implementa el selector.

(bueno, eso se convirtió en más de un libro de lo esperado … esperemos que aún sea útil!)