简体   繁体   English

iPhone仿真器和真实设备在消息转发方面的不同行为

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

I want to use Message Forwarding to let any unimplemented getter method return 0, instead of throw a unrecognized selector exception. 我想使用Message Forwarding让任何未实现的getter方法返回0,而不是抛出一个无法识别的选择器异常。 Like 喜欢

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)

So I wrote this example: 所以我写了这个例子:

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

The output result on a real device is 0, 0.00000, (null), but on emulator, it's 0, NaN, (null) 实际设备上的输出结果是0,0.00000,(null),但在模拟器上,它是0,NaN,(null)

So the double type does not work as expected. 所以double类型不能按预期工作。 My first thought is change the NSMethodSignature to "d@:" (d is double) 我的第一个想法是将NSMethodSignature更改为“d @:”(d为double)

The output result is right on both device and simulator, but there are something wierd happening on the simulator only. 输出结果在设备和模拟器上都是正确的,但是模拟器上只有一些奇怪的事情发生。 Run this code and it will crash on the 6th loop with some kind of CALayer exception: 运行此代码,它将在第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];
}

I'm curious about two questions, why NaN is returned on emulator in the first example, and what happened on the second example? 我很好奇两个问题,为什么NaN在第一个例子中在模拟器上返回,以及在第二个例子中发生了什么?

For your first question, this what I have found, on simulator 对于你的第一个问题,这是我在模拟器上找到的

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

So clearly NAN(fff8000000000000) is returned instead of 0.0. 很明显返回NAN(fff8000000000000)而不是0.0。

To look deeper on what is different between [NSMethodSignature signatureWithObjCTypes:"d@:"] and [NSMethodSignature signatureWithObjCTypes:"Q@:"] , look this 要深入了解[NSMethodSignature signatureWithObjCTypes:"d@:"][NSMethodSignature signatureWithObjCTypes:"Q@:"] ,请查看此

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

output 产量

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

You can see on the second method signature have flags {isFloat} on return value. 您可以在第二个方法签名上看到返回值上有flags {isFloat} I am not expert on x86 and AMR and low-level ObjC runtime. 我不是x86和AMR以及低级ObjC运行时的专家。 But I think the CPU used this flag to identify the type of return value. 但我认为CPU使用此标志来标识返回值的类型。 Without setting it on x86 CPU, the expected float return value is therefore interpreted as NAN. 如果不在x86 CPU上设置,则预期的浮点返回值将被解释为NAN。


For your second question, I think it is because you tell the runtime that it will return a 64bit size value, and thus a memory of 64bit size on stack is zeroed. 对于你的第二个问题,我认为这是因为你告诉运行时它将返回64位大小的值,因此堆栈上64位大小的内存被清零。 However, the caller is expecting a 32bit return size (NSInteger). 但是,调用者期望返回32位(NSInteger)。 Therefore some kind of stackoverflow is happened and lead to the crash. 因此发生了某种堆栈溢出并导致崩溃。


I actually implemented something similar, aiming to make NSNull works like nil . 我实际上实现了类似的东西,旨在使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];
}

If you want to use Message Forwarding to let any unimplemented getter method return 0, instead of throw a unrecognized selector exception, perhaps you can instead use +resolveInstanceMethod ? 如果你想使用Message Forwarding让任何未实现的getter方法返回0,而不是抛出一个无法识别的选择器异常,也许你可以使用+ resolveInstanceMethod?

Here's an example of returning an NSString. 这是返回NSString的示例。 You'll have to tweak it to return a primitive. 你必须调整它才能返回原语。 let me know if you have trouble. 如果你遇到麻烦,请告诉我。

If you're using ARC, you need a bridge cast on the void* too. 如果您正在使用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;
}

When we use class_addMethod, the third parameter is the type codes. 当我们使用class_addMethod时,第三个参数是类型代码。 . . A good way to work them out is to make a real method, and then introspect on it. 解决问题的一个好方法是制作一个真正的方法,然后对其进行反思。 Here's a utility to return the type codes for a (real) selector on a class: https://github.com/jasperblues/spring-objective-c/blob/master/Source/ … – user404201 6 mins ago 这是一个实用程序,用于返回类上(真实)选择器的类型代码: 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