2013-01-13 9 views
6

Chcę użyć przekazywania wiadomości, aby dowolna niezaimplementowana metoda pobierająca zwróciła 0, zamiast wyrzucać nierozpoznany wyjątek selektora. JakRóżne zachowanie emulatora iPhone'a i urządzenia rzeczywistego dotyczące przekazywania wiadomości

MyClass *r = [[MyClass alloc] init]; 
NSNumber *n = (NSNumber *)r; 
NSLog(@"%d", [n integerValue]); // output 0 
NSLog(@"%f", [n doubleValue]); // output 0.00000 
NSLog(@"%@", [n stringValue]); // output (null) 

Więc napisałem ten przykład:

#pragma mark - 
#pragma mark Application lifecycle 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  

    NSNumber *n = (NSNumber *)self; 
    NSLog(@"%d", [n integerValue]); 
    NSLog(@"%f", [n doubleValue]); 
    NSLog(@"%@", [n stringValue]); 

    return YES; 
} 

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { 
    NSMethodSignature *ms = [super methodSignatureForSelector:aSelector]; 
    if(ms) 
     return ms; 

    // Q = uint64_t, so it should also works for double which is also 64bit 
    return [NSMethodSignature signatureWithObjCTypes:"[email protected]:"]; 
} 

- (void)forwardInvocation:(NSInvocation *)anInvocation { 
    uint64_t ZERO64 = 0; 
    [anInvocation setReturnValue:&ZERO64]; 
} 

Wynik wyjście na prawdziwym urządzeniu wynosi 0, 0,00000 (null), ale na emulatorze, to 0, NaN (null)

Tak więc podwójny typ nie działa zgodnie z oczekiwaniami. Moją pierwszą myślą jest zmiana NSMethodSignature na "d @:" (d jest podwójne)

Wynik wyjściowy jest prawidłowy dla obu urządzeń i symulatorów, ale na symulatorze dzieje się coś dziwnego. Uruchom ten kod i będzie rozbić na 6 pętli z jakimś wyjątkiem CALayer:

#pragma mark - 
#pragma mark Application lifecycle 

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {  

    for(NSInteger i = 0; i < 100; i++) { 
     NSInteger t = [(NSNumber *)self integerValue]; 

     UIViewController *view = [[UIViewController alloc] init]; 
     // it always crash on the 6th loop on this line** 
     UINavigationController *nc = [[UINavigationController alloc] initWithRootViewController:view]; 
    } 

    return YES; 
} 

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { 
    NSMethodSignature *ms = [super methodSignatureForSelector:aSelector]; 
    if(ms) 
     return ms; 

    // we change to return double 
    return [NSMethodSignature signatureWithObjCTypes:"[email protected]:"]; 
} 

- (void)forwardInvocation:(NSInvocation *)anInvocation { 
    uint64_t ZERO64 = 0; 
    [anInvocation setReturnValue:&ZERO64]; 
} 

Ciekawi mnie dwa pytania, dlaczego zwracany jest NAN na emulatorze w pierwszym przykładzie, a co się stało na sekundę przykład?

+0

Ściśle związane: [Dlaczego wysyłanie dowolnego selektora do obiektu zerowego nic nie robi, ale wysłanie "Nieprawidłowy" selektor do dowolnego NSObject podnosi wyjątek?] (Http://stackoverflow.com/questions/11530133/why-is-it-that-sending-any-selector-to-a-nil-object-does-nothing -but-sending-an/11531609 # 11531609) i zobacz moją odpowiedź na implementację. –

Odpowiedz

2

Na pierwsze pytanie, to co znalazłem, na symulatorze

union { 
    double d; 
    uint64_t l; 
} u; 
NSNumber *n = (NSNumber *)self; 
u.d = [n doubleValue]; 
NSLog(@"%f", u.d); // nan 
NSLog(@"%llx",u.l); // fff8000000000000 
bzero(&u, sizeof(double)); 
NSLog(@"%f", u.d); // 0.000000 
NSLog(@"%llx",u.l); // 0 

Tak wyraźnie NAN (fff8000000000000) jest zwracana zamiast 0.0.

Aby spojrzeć głębiej na tym, co różni się między [NSMethodSignature signatureWithObjCTypes:"[email protected]:"] i [NSMethodSignature signatureWithObjCTypes:"[email protected]:"], wyglądać to

NSLog(@"%@\n%@", [[NSMethodSignature signatureWithObjCTypes:"[email protected]:"] debugDescription], [[NSMethodSignature signatureWithObjCTypes:"[email protected]:"] debugDescription]); 

wyjście

<NSMethodSignature: 0x74a0950> 
    number of arguments = 2 
    frame size = 8 
    is special struct return? NO 
    return value: -------- -------- -------- -------- 
     type encoding (Q) 'Q' 
     flags {} 
     modifiers {} 
     frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0} 
     memory {offset = 0, size = 8} 
    argument 0: -------- -------- -------- -------- 
     type encoding (@) '@' 
     flags {isObject} 
     modifiers {} 
     frame {offset = 0, offset adjust = 0, size = 4, size adjust = 0} 
     memory {offset = 0, size = 4} 
    argument 1: -------- -------- -------- -------- 
     type encoding (:) ':' 
     flags {} 
     modifiers {} 
     frame {offset = 4, offset adjust = 0, size = 4, size adjust = 0} 
     memory {offset = 0, size = 4} 

<NSMethodSignature: 0x74a1e80> 
    number of arguments = 2 
    frame size = 8 
    is special struct return? NO 
    return value: -------- -------- -------- -------- 
     type encoding (d) 'd' 
     flags {isFloat} <<<<----- this flag should be set if the return value is float type 
     modifiers {} 
     frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0} 
     memory {offset = 0, size = 8} 
    argument 0: -------- -------- -------- -------- 
     type encoding (@) '@' 
     flags {isObject} 
     modifiers {} 
     frame {offset = 0, offset adjust = 0, size = 4, size adjust = 0} 
     memory {offset = 0, size = 4} 
    argument 1: -------- -------- -------- -------- 
     type encoding (:) ':' 
     flags {} 
     modifiers {} 
     frame {offset = 4, offset adjust = 0, size = 4, size adjust = 0} 
     memory {offset = 0, size = 4} 

widać na drugim podpisem sposobem mają flags {isFloat} na wartości zwracanej. Nie jestem ekspertem od x86 i AMR oraz niskiego poziomu runtime ObjC. Myślę jednak, że procesor użył tej flagi do określenia typu zwracanej wartości. Nie ustawiając go na procesorze x86, oczekiwana wartość powrotu jest interpretowana jako NAN.


Na drugie pytanie, myślę, że to dlatego, że środowisko wykonawcze powiedzieć, że będzie zwracać wartość rozmiarze 64bit, a tym samym pamięć o wielkości 64-bitowej na stosie zostaje wyzerowany. Jednak wywołujący oczekuje 32-bitowego rozmiaru zwracanego (NSInteger). Dlatego nastąpił pewien rodzaj stackoverflow i doprowadzić do katastrofy.


I rzeczywiście realizowane coś podobnego, dążąc do NSNull działa jak nil.

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { 
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector]; 
    if (signature) 
     return signature; 

    const Class forwardClasses[] = {[NSNumber class], [NSString class], [NSArray class], [NSOrderedSet class]}; // add new classes if you think the list is not enough 

    for (int i = 0; i < sizeof(forwardClasses)/sizeof(Class); i++) { 
     Class cls = forwardClasses[i]; 
     signature = [cls instanceMethodSignatureForSelector:aSelector]; 
     if (signature) { 
      return signature; 
     } 
    } 

    return signature; 
} 

- (void)forwardInvocation:(NSInvocation *)anInvocation { 
    NSUInteger len = [[anInvocation methodSignature] methodReturnLength]; 
    char buff[len]; 
    bzero(buff, len); 
    [anInvocation setReturnValue:buff]; 
} 
1

Jeśli chcesz użyć Message Forwarding pozwolić żadnej niezaimplementowane getter Sposób powrotu 0, zamiast rzucać nierozpoznane wyjątek wyboru, być może można zamiast tego użyć + resolveInstanceMethod?

Oto przykład zwracania NSString. Będziesz musiał dostosować go, aby zwrócić prymityw. daj mi znać, jeśli masz problemy.

Jeśli używasz ARC, potrzebujesz też rzutu mostu na pustce *.

+ (BOOL)resolveInstanceMethod:(SEL)sel 
{ 
    NSString* name = NSStringFromSelector(sel); 

    IMP imp = imp_implementationWithBlock((void*) objc_unretainedPointer(^(id me, BOOL selected) 
    { 
     return @"Hello!"; 
    })); 
    class_addMethod(self, sel, imp, "@"); //The type '@' is an object. For int use 'i'. Google "obj-c runtime types" 
    return YES; 
} 

Kiedy używamy class_addMethod, trzecim parametrem są kody typów. . . Dobrym sposobem na ich sprawdzenie jest stworzenie prawdziwej metody, a następnie introspekcja na jej temat. Oto narzędzie do zwracania kodów typów selektora (rzeczywistego) w klasie: https://github.com/jasperblues/spring-objective-c/blob/master/Source/ ... - user404201 6 minut temu

+0

nie masz możliwości utworzenia bloku, który zwróci poprawną wartość typu: –

+0

@ xlc0212, Kiedy używamy class_addMethod, trzecim parametrem są kody typów. . . Dobrym sposobem na ich sprawdzenie jest stworzenie prawdziwej metody, a następnie introspekcja na jej temat. Możesz wywołać metodę typeCodesForSelector na tej klasie: https://github.com/jasperblues/spring-objective-c/blob/master/Source/Utils/SpringIntrospectionUtils.m –

+0

@ xlc0212, Właśnie przeczytałem twoją odpowiedź i jest lepiej . . Może zostanę tutaj, ponieważ informacje są nadal przydatne. –

Powiązane problemy