ultima tutorial objective logo español objective-c

objective-c - tutorial - objective c vs c++



¿Es seguro usar isKindOfClass frente a una instancia de NSString para determinar el tipo? (4)

Desde isKindOfClass: documentación de método en NSObject:

Tenga cuidado al usar este método en objetos representados por un clúster de clase. Debido a la naturaleza de los grupos de clases, el objeto que obtienes no siempre es del tipo esperado.

La documentación procede a dar un ejemplo de por qué nunca debe preguntar algo como el siguiente de una instancia de NSArray:

// DO NOT DO THIS! if ([myArray isKindOfClass:[NSMutableArray class]]) { // Modify the object }

Ahora, para dar un ejemplo de un uso diferente, digamos que tengo una instancia de NSObject en la que me gustaría determinar si tengo un NSString o NSArray.

Ambos tipos son clusters de clases, pero la documentación anterior muestra que el peligro radica en la respuesta a isKindOfClass: ser demasiado afirmativo (respondiendo SÍ algunas veces cuando realmente no se tiene una matriz mutable) mientras se hace una pregunta sobre membresía simple en un clúster aún sería válido.

Un ejemplo:

NSObject *originalValue; // originalValue gets set to some instance if ( [originalValue isKindOfClass:[NSString class]] ) // Do something with string

¿Es correcta esta suposición? ¿Es realmente seguro usar isKindOfClass: contra instancias de clúster de clase para determinar la membresía? Estoy específicamente interesado en la respuesta para el omnipresente NSString, NSArray y NSDictionary, pero me gustaría saber si es generalizable.


Consideremos el siguiente código:

if ([dict isKindOfClass:[NSMutableDictionary class]]) { [(NSMutableDictionary*)dict setObject:@1 forKey:@"1"]; }

Creo que el verdadero propósito de la nota de Apple citada en la pregunta es que este código podría provocar bloqueos. Y el problema aquí radica en la creación de puentes gratuitos con CoreFoundation. Vamos a considerar en más detalles 4 variantes:

NSDictionary* dict = [NSDictionary new]; NSMutableDictionary* dict = [NSMutableDictionary new]; CFDictionaryRef dict = CFDictionaryCreate(kCFAllocatorDefault, NULL, NULL, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFCopyStringDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);

De hecho, en TODAS las 4 variantes, la verdadera clase del dict será __NSCFDiccionario. Y las 4 variantes pasarán la prueba en el primer fragmento de código. Pero en 2 casos (declaraciones NSDictionary y CFDictionaryRef) fallaremos con log algo así:

* Aplicación de finalización debido a la excepción no detectada ''NSInternalInconsistencyException'', razón: ''- [__ NSCFDictionary setObject: forKey:]: método de mutación enviado al objeto inmutable''

Entonces, las cosas se aclararon un poco. En 2 casos, podemos modificar el objeto y en 2 casos no. Depende de la función de creación, y probablemente el estado de mutabilidad sea rastreado por el propio objeto __NSCFDictionary.

Pero, ¿por qué usamos la misma clase para objetos mutables e inmutables? Posible respuesta, debido a las limitaciones del lenguaje C (y CoreFoundation es una API C, como sabemos). ¿Qué problema tenemos en C? Las declaraciones de tipos de diccionario mutable e inmutable deberían reflejar lo siguiente: CFMutableDictionaryRef type es un subtipo de CFDictionaryRef. Pero C no tiene mecanismos para esto. Pero queremos poder pasar el objeto CFMutableDictionary en función esperando el objeto CFDictionary, y preferiblemente sin que el compilador emita advertencias molestas. ¿Que tenemos que hacer?

Echemos un vistazo a las siguientes declaraciones de tipos de CoreFoundation:

typedef const struct __CFDictionary * CFDictionaryRef; typedef struct __CFDictionary * CFMutableDictionaryRef;

Como podemos ver aquí, CFDictionary y CFMutableDictionary están representados por el mismo tipo, y difieren solo en el modificador const. Entonces, aquí comienzan los problemas.

Lo mismo generalmente se aplica a NSArray también, pero la situación es un poco más complicada. Cuando crea NSArray o NSMutableArray directamente, obtendrá algunas clases especiales (__NSArrayI y __NSArrayM respectivamente). Para este choque de clases no se reproduce. Pero cuando crea CFArray o CFMutableArray, las cosas siguen siendo las mismas. ¡Así que ten cuidado!


Estás leyendo la advertencia mal. Todo lo que dice es que, solo porque está representado internamente como algo que es mutable, no significa que debas tratar de mutarlo, porque mutarlo podría violar el contrato entre ti y quienquiera que hayas obtenido la matriz; podría violar sus suposiciones de que no lo mutarás.

Sin embargo, la prueba dada por isKindOfClass: definitivamente sigue siendo válida. Si [something isKindOfClass:[NSMutableArray class]] , entonces es una instancia de NSMutableArray o subclase del mismo, lo que significa que es mutable.


Esta es una advertencia extraña en los documentos de Apple. Es como decir: "tenga cuidado con esta función, porque si la usa con otro código roto, no funcionará". Pero puedes decir eso con cualquier cosa.

Si el diseño sigue el principio de sustitución, que debería, entonces isKindOfClass funcionará. ( wikipedia: Principio de sustitución de Liskov )

Si algún código viola el principio, devolviendo una instancia de una subclase NSMutableArray que no responde al addObject: y otros métodos NSMutableArray, entonces ese código tiene un problema ... a menos que sea solo un hack temporal a pequeña escala ...


La advertencia en la documentación usa el ejemplo NSMutableArray . Supongamos que algún desarrollador usa NSMutableArray como clase base para su propia implementación de un nuevo tipo de matriz CoolFunctioningArray . Este CoolFunctioningArray por diseño no es mutable. Sin embargo, isKindOfClass devolverá YES a isKindOfClass:[NSMutableArray class] , que es verdadero, pero por diseño no lo es.

isKindOfClass: devolverá YES si el receptor en algún lugar hereda de la clase pasada como argumento. isMemberOfClass: devolverá YES solo si el receptor es una instancia de la clase aprobada solo como argumento, es decir, sin incluir subclases.

Generalmente isKindOfClass: no es seguro de usar para probar la membresía. Si una instancia devuelve YES para isKindOfClass:[NSString class] , solo sabe que responderá a todos los métodos definidos en la clase NSString , pero no sabrá con certeza qué podría hacer la implementación de esos métodos. Alguien podría haber subclasificado NSString para generar una excepción para el método de longitud.

Creo que podrías usar isKindOfClass: para este tipo de pruebas, especialmente si estás trabajando con tu propio código que (como buen samaritano) diseñaste de tal manera que responderá de una manera que todos esperamos (por ejemplo, método de longitud de una subclase NSString para devolver la longitud de la cadena que representa en lugar de generar una excepción). Si está utilizando muchas bibliotecas externas de desarrolladores raros (como el desarrollador de CoolFunctioningArray , que debe tomarse), debe utilizar el método isKindOfClass con precaución, y preferiblemente utilizar el método isMemberOfClass: (posiblemente varias veces para probar la membresía) de un grupo de clases).