2009-09-03 16 views
5

Jeśli mam dwie klasy, podklasy i nadklasy:Czy istnieje odpowiednik dynamicznej obsady C++ w 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"); 

mogę osiągnąć to samo w Objective-C z isMemberOfClass następująco:

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"); 

Czy istnieje prostszy sposób niż to?

(PS Wiem, że nie muszę używać dodatkowej zmiennej id, ale wtedy musiałbym wymusić rzutowanie super na SubClass *, co czasami powodowałoby nieprawidłowe odwołanie, które musiałbym później posprzątać. że realizacja jest jednak mniej rozwlekły, a to poniżej)

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

'sprawdza dynamic_cast' jeśli obiekt jest, że klasa lub jeden z jego podklasy. Różni się to od 'isMemberOfClass:', które sprawdza tylko dla tej klasy. 'dynamic_cast' jest równoważne' isKindOfClass: 'co również sprawdza podklasy. – user102008

Odpowiedz

4

Możesz dodać kategorię na NSObject, aby dodać pożądaną funkcjonalność.

//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 

Następnie można to zrobić:

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

Wadą jest to, że powrót nie jest już poprawnie wpisanym wskaźnikiem. Jak wspomina Chris Suter poniżej (choć unikam ich tak bardzo, jak to możliwe) makro może być lepsze do tego celu. – jbenet

+1

@jbennet: Nie marnowałbym zbyt wiele czasu na próbę dodania rodzajów zabezpieczeń w środowisku wykonawczym do dynamicznego języka, takiego jak Objective-C. Jest to sprzeczne z podstawową filozofią tego języka. Zamiast tego upewnij się, że masz wiele testów jednostkowych, które weryfikują poprawność twojego kodu modelu. Ma to również dodatkową zaletę polegającą na łapaniu błędów za każdym razem, gdy budujesz, nie wtedy, gdy użytkownik uruchamia aplikację na wolności, co i tak jest za późno. – PeyloW

+0

@jbenet Nowsze wersje xCode zapewniają 'instancetype', który pozwala na ulepszenie [typ bezpieczniejszego wariantu] (http://stackoverflow.com/a/26803004/2547229), na który dodałem odpowiedź. – Benjohn

0

Wydaje mi się, że mogę użyć operatora potrójny umieścić to wszystko w jednej linii, ale to jeszcze trochę bałaganu:

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"); 

Jeśli otrzymam zmienną z powrotem z funkcji, będę musiał boleć go w zmiennej identyfikacyjnej, aby ta wersja działała.

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

Cóż, możesz teraz wziąć to i sprawić, że będzie to funkcja. 'id dynCast (Class subClass, id object)'. Prawdopodobnie chcesz 'isKindOfClass:' powyżej 'isMemberOfClass'. Możesz myśleć, że wiesz, jakie są twoje zajęcia, ale coś takiego jak KVO może dynamicznie podklasować i zmienić klasę twoich obiektów. – Ken

+0

Cały punkt dynamic_cast polega na tym, aby dowiedzieć się, kiedy masz konkretny podtyp (abyś mógł bezpiecznie wywoływać metody specyficzne dla podtypów). isKindOfClass również dałby mi pod-podtypy, które mogę lub nie chcę w zależności od okoliczności. Biorąc to pod uwagę, nie dostałem jeszcze do KVO, więc mogę mówić z mojego tyłu .... –

+0

Mówię, że chcesz sub-typy też, niezależnie od tego, co robi C++. :-) Sub podtypy nadal odpowiadają na wiadomości podtypu, a kakao jest zbyt dynamiczne, aby móc liczyć na konkretną podklasę, nawet jeśli napisałeś podklasę. – Ken

0

zasadzie ..

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

nie wydaje się dużo bardziej rozwlekły.

+0

Tak, ale sub_pointer nie jest już wpisanym wskaźnikiem, a jeśli foo jest metodą, musisz wywołać ją dwukrotnie lub użyć dodatkowego mechanizmu pamięci. –

4

używam makra:

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

I marginalnie wolę je do korzystania kategorię na NSObject ponieważ zwrócony obiekt jest odpowiedniego typu (a nie id), chociaż zdaję sobie sprawę, że w większości przypadków po prostu przypisuje się je do zmiennej tego samego typu.

+0

Hej Chris, czy istnieje powód, dla którego używasz dodatkowego 'cls * inst_ = (cls *) (x);'? Dlaczego nie tylko: '[x isKindOfClass: [klasa kls]]? (cls *) x: zero; '? – jbenet

+2

@ jbenet-Tak. Jest to standardowe narzędzie, którego należy się wystrzegać w makrach. W twoim przypadku x zostanie dwukrotnie ocenione, więc jeśli użyjesz makra w ten sposób: 'DYNAMIC_CAST (someFunc(), SomeClass)', 'someFunc' zostanie wywołane dwa razy, co nie jest tym, czego potrzebujesz. –

+0

Ah! dobrze!! nie myślałem o tym :) – jbenet

2

Jeśli wolno rzucać żadnych C++ do mieszanki można uniknąć makr i uzyskać odpowiedni typ z rutyny Szablon:

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

Potem rozmowa będzie po prostu wyglądać następująco:

sub_pointer = objc_cast<SubClass>(super); 
1

Nowsze wersje xCode zapewniają instancetype i pozwalają na bardzo schludne rozwiązanie oparte na kategoriach.Korzystanie z niego wygląda następująco:

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

Kategoria Oświadczenie o 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 

Kategoria Realizacja

@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 

Wykorzystanie

Jest to metoda klasa wpłacone na dowolny klasa posiadająca NSObject jako super klasa (czyli w zasadzie każda klasa).

wysyłać metodę tryCast: do klasy masz nadzieję oddać do, z obiektu, który chcesz lanego jako parametr. Podoba się to [ClassIWant tryCast: thingIWantCasted].

Przykład zastosowania

- (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. 
      ... 
Powiązane problemy