2009-09-03 11 views
5

Si tengo dos clases, subclase y superclase:¿Hay un equivalente al lanzamiento dinámico de C++ en Objective-C?

SuperClass *super = new SuperClass(); 
SubClass *sub = new SubClass(); 
SubClass *sub_pointer; 

// **The nice one-line cast below** 
sub_pointer = dynamic_cast<SubClass*> super; 
// Prints NO 
printf("Is a subclass: %s\n", sub_pointer ? "YES" : "NO"); 

sub_pointer = dynamic_cast<SubClass*> sub; 
// Prints YES 
printf("Is a subclass: %s\n", sub_pointer ? "YES" : "NO"); 

puedo lograr lo mismo en Objective-C con isMemberOfClass de la siguiente manera:

SuperClass *super = [[SuperClass alloc] init]; 
SubClass *sub = [[SubClass alloc] init]; 
SubClass *sub_pointer; 
id generic_pointer; 

// Not as easy: 
generic_pointer = super; 
if ([generic_pointer isMemberOfClass:[SubClass class]]) { 
    sub_pointer = generic_pointer; 
} else { 
    sub_pointer = nil; 
} 
// Logs NO 
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO"); 

generic_pointer = sub; 
if ([generic_pointer isMemberOfClass:[SubClass class]]) { 
    sub_pointer = generic_pointer; 
} else { 
    sub_pointer = nil; 
} 
// Logs YES 
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO"); 

¿Existe una manera más fácil que esto?

(PD Sé que no tengo que usar la variable de id. Adicional, pero entonces tendría que forzar el molde super a SubClass *, lo que algunas veces resultaría en una referencia inválida que tendría que limpiar después. que la aplicación, sin embargo, es menos palabras, y es a continuación)

SuperClass *super = [[SuperClass alloc] init]; 
SubClass *sub = [[SubClass alloc] init]; 
SubClass *sub_pointer; 

// Not as easy: 
sub_pointer = (SubClass*) super; 
if (![sub_pointer isMemberOfClass:[SubClass class]]) { 
    sub_pointer = nil; 
} 
// Logs NO 
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO"); 

sub_pointer = (SubClass*) sub; 
if (![sub_pointer isMemberOfClass:[SubClass class]]) { 
    sub_pointer = nil; 
} 
// Logs YES 
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO"); 
+0

'controles dynamic_cast' si el objeto es esa clase o una de sus subclases. Esto es diferente de 'isMemberOfClass:' que solo busca esa clase. 'dynamic_cast' es equivalente a' isKindOfClass: 'que también verifica las subclases. – user102008

Respuesta

4

Puede agregar una categoría en NSObject para agregar la funcionalidad que desee.

//NSObject+DynamicCast.h 
@interface NSObject (DynamicCast) 
-(id)objectIfMemberOfClass:(Class)aClass; 
@end 

//NSObject+DynamicCast.m 
@implementation NSObject (DynamicCast) 
-(id)objectIfMemberOfClass:(Class)aClass; 
{ 
    return [self isMemberOfClass:aClass] ? self : nil; 
} 
@end 

Posteriormente, se podría hacer esto:

SuperClass *super = [[SuperClass alloc] init]; 
SubClass *sub = [[SubClass alloc] init]; 
SubClass *sub_pointer; 
id generic_pointer; 

// **The nice one-line cast below** 
sub_pointer = [super objectIfMemberOfClass:[SubClass class]]; 
// Prints NO 
printf("Is a subclass: %s\n", sub_pointer ? "YES" : "NO"); 

sub_pointer = [sub objectIfMemberOfClass:[SubClass class]]; 
// Prints YES 
printf("Is a subclass: %s\n", sub_pointer ? "YES" : "NO"); 
+0

La desventaja aquí es que el retorno ya no es un puntero tipeado correctamente. Como Chris Suter a continuación señala, (aunque los evito tanto como sea posible) una macro puede ser mejor para este propósito. – jbenet

+1

@jbennet: No desperdiciaría demasiado tiempo tratando de agregar typesafety en tiempo de ejecución a un lenguaje dinámico como Objective-C. Está en contra de la filosofía fundamental del lenguaje. En su lugar, asegúrese de tener un lote de pruebas unitarias que verifiquen la exactitud de su código modelo. Esto también tiene la ventaja añadida de detectar errores cada vez que construyes, no cuando el usuario ejecuta la aplicación en libertad, que de todos modos es demasiado tarde. – PeyloW

+0

@jbenet Las versiones más recientes de xCode proporcionan 'instancetype' que permite una [variante más segura] mejorada (http://stackoverflow.com/a/26803004/2547229) para la que he agregado una respuesta. – Benjohn

0

se me ocurre que puedo utilizar el operador ternario a poner todo esto en una sola línea, pero aún así es un poco de un desastre:

SuperClass *super = [[SuperClass alloc] init]; 
SubClass *sub = [[SubClass alloc] init]; 
SubClass *sub_pointer; 

// One line, but still a bit wordy 
sub_pointer = [super isMemberOfClass:[SubClass class]] ? (SubClass*) super : nil; 
// Logs NO 
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO"); 

sub_pointer = [sub isMemberOfClass:[SubClass class]] ? (SubClass*) sub : nil; 
// Logs YES 
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO"); 

Si obtengo la variable deseada de una función, tendré que c Dolor en una variable id para que esta versión funcione.

SubClass *sub_pointer; 
id generic_pointer; 

// One line, but still a bit wordy 
generic_pointer = (id) mySuperFunc(); 
sub_pointer = [generic_pointer isMemberOfClass:[SubClass class]] ? generic_pointer : nil; 
// Logs NO 
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO"); 

generic_pointer = (id) mySubFunc(); 
sub_pointer = [generic_pointer isMemberOfClass:[SubClass class]] ? generic_pointer : nil; 
// Logs YES 
NSLog(@"Is a subclass: %@", sub_pointer ? @"YES" : @"NO"); 
+0

Bueno, ahora puedes tomar eso y hacerlo funcionar. 'id dynCast (clase subclase, objeto id)'. Probablemente quiera 'isKindOfClass:' sobre 'isMemberOfClass'. Puede pensar que sabe cuáles son sus clases, pero algo como KVO puede subclasificar dinámicamente y cambiar la clase de sus objetos. – Ken

+0

El objetivo de dynamic_cast es averiguar cuándo tienes el subtipo específicamente (para que puedas llamar de manera segura a los métodos específicos del subtipo). isKindOfClass también me da sub-subtipos, que puedo o no querer dependiendo de la circunstancia. Dicho esto, no he llegado aún a KVO, por lo que puede estar hablando de mi trasero ... –

+0

Estoy diciendo que quieres subtipos secundarios también, independientemente de lo que hace C++. :-) Los subtipos secundarios aún responden a mensajes específicos de subtipo, y Cocoa es demasiado dinámico para poder contar con la posibilidad de obtener _exactamente_ una subclase particular, incluso cuando usted escribió la subclase. – Ken

0

básicamente ..

id sub_pointer = [foo isMemberOfClass:AClass] ? foo : nil; 
NSLog(@"Is a subclass: %i", sub_pointer!=nil); 

no parece mucho más prolijo.

+0

Sí, pero sub_pointer ya no es un puntero tipeado, y si foo es un método, tiene que llamarlo dos veces, o utilizar un mecanismo de almacenamiento adicional. –

4

que utiliza una macro:

#define DYNAMIC_CAST(x, cls)        \ 
    ({              \ 
    cls *inst_ = (cls *)(x);        \ 
    [inst_ isKindOfClass:[cls class]] ? inst_ : nil;  \ 
    }) 

I marginalmente prefiero que el uso de una categoría en NSObject porque el objeto devuelto es del tipo correcto (en lugar de identificación), aunque me doy cuenta de que en la mayoría de los casos simplemente lo asignarás a una variable del mismo tipo de todos modos.

+0

Hola Chris, ¿hay alguna razón por la que utilizas los 'cls * adicionales inst_ = (cls *) (x);'? ¿Por qué no simplemente: '[x isKindOfClass: [cls class]]? (cls *) x: nil; '? – jbenet

+2

@ jbenet-Sí. Este es un estándar que hay que tener en cuenta en macros. En su caso, x se evaluará dos veces, por lo que si utilizara la macro de esta manera: 'DYNAMIC_CAST (someFunc(), SomeClass)', 'someFunc' se llamaría dos veces, lo que no es lo que desea. –

+0

¡Ah! ¡¡derecho!! no pensé en eso :) – jbenet

2

Si se le permite lanzar cualquier C++ en la mezcla puede evitar macros y obtener el tipo correcto con una rutina de plantilla:

template <typename T, typename U> 
inline T* objc_cast(U* instance) 
{ 
    return [instance isMemberOfClass:[T class]] ? 
       static_cast<T*>(instance) : 
       nil; 
} 

A continuación, la llamada podría ser similar a:

sub_pointer = objc_cast<SubClass>(super); 
1

Las versiones más recientes de xCode proporcionan instancetype y permiten una solución basada en categorías muy ordenada.El uso que tiene este aspecto:

TypeIWant *const thingIWant = [TypeIWant tryCast: thingIWantToCast]; 

Declaración Categoría de NSObject

@interface NSObject (DynamicCast) 
// Try a dynamic cast. Return nil if the class isn't compatible. 
+(instancetype) tryCast: (id) toCast; 

// Check a dynamic cast. Throw if the class isn't compatible. 
+(instancetype) checkCast: (id) toCast; 
@end 

Categoría Implementación

@implementation NSObject (DynamicCast) 

+(instancetype) tryCast: (id) toCast 
{ 
    return [toCast isKindOfClass: self] ? toCast : nil; 
} 

+(instancetype) checkCast:(id)toCast 
{ 
    const id casted = [self tryCast: toCast]; 
    if(!casted) 
    { 
     [NSException raise: NSInvalidArgumentException format: @"Can't cast %@ to be an %@", toCast, NSStringFromClass(self)]; 
    } 
    return casted; 
} 

@end 

Uso

Este es un método clase rescatable en ninguna clase que tiene NSObject como una súper clase (así que, básicamente, cualquier clase).

de enviar el tryCast: método a la clase que espera lanzar a, con el objeto que desea fundido como parámetro. Me gusta esto [ClassIWant tryCast: thingIWantCasted].

Ejemplo de uso

- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender 
{ 
    UIPopoverController *const popoverDestination = 
    [UIStoryboardPopoverSegue tryCast: segue].popoverController; 
    if(popoverDestination) 
    { 
     UITableViewCell *const tableViewSender = 
     [UITableViewCell tryCast: sender]; 
     if(tableViewSender) 
     { 
      // Things you need to do in this case. 
      ... 
Cuestiones relacionadas