簡體   English   中英

iPhone仿真器和真實設備在消息轉發方面的不同行為

[英]Different behavior on iPhone Emulator and Real Device about Message Forwarding

我想使用Message Forwarding讓任何未實現的getter方法返回0,而不是拋出一個無法識別的選擇器異常。 喜歡

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)

所以我寫了這個例子:

#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:"Q@:"];
}

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

實際設備上的輸出結果是0,0.00000,(null),但在模擬器上,它是0,NaN,(null)

所以double類型不能按預期工作。 我的第一個想法是將NSMethodSignature更改為“d @:”(d為double)

輸出結果在設備和模擬器上都是正確的,但是模擬器上只有一些奇怪的事情發生。 運行此代碼,它將在第6個循環中崩潰,並出現某種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:"d@:"];
}

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

我很好奇兩個問題,為什么NaN在第一個例子中在模擬器上返回,以及在第二個例子中發生了什么?

對於你的第一個問題,這是我在模擬器上找到的

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

很明顯返回NAN(fff8000000000000)而不是0.0。

要深入了解[NSMethodSignature signatureWithObjCTypes:"d@:"][NSMethodSignature signatureWithObjCTypes:"Q@:"] ,請查看此

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

產量

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

您可以在第二個方法簽名上看到返回值上有flags {isFloat} 我不是x86和AMR以及低級ObjC運行時的專家。 但我認為CPU使用此標志來標識返回值的類型。 如果不在x86 CPU上設置,則預期的浮點返回值將被解釋為NAN。


對於你的第二個問題,我認為這是因為你告訴運行時它將返回64位大小的值,因此堆棧上64位大小的內存被清零。 但是,調用者期望返回32位(NSInteger)。 因此發生了某種堆棧溢出並導致崩潰。


我實際上實現了類似的東西,旨在使NSNullnil NSNull工作。

- (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];
}

如果你想使用Message Forwarding讓任何未實現的getter方法返回0,而不是拋出一個無法識別的選擇器異常,也許你可以使用+ resolveInstanceMethod?

這是返回NSString的示例。 你必須調整它才能返回原語。 如果你遇到麻煩,請告訴我。

如果您正在使用ARC,那么您也需要在void *上進行橋接。

+ (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;
}

當我們使用class_addMethod時,第三個參數是類型代碼。 解決問題的一個好方法是制作一個真正的方法,然后對其進行反思。 這是一個實用程序,用於返回類上(真實)選擇器的類型代碼: https//github.com/jasperblues/spring-objective-c/blob/master/Source/ ... - user404201 6分鍾前

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM