Different messaging behavior on iPhone Emulator and Real Device

I want to use message forwarding so that any unimplemented getter method returns 0 instead of throwing a thrown exception exception. how

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 the real device is 0, 0.00000, (null), but on the emulator it is 0, NaN, (null)

Thus, the double type does not work as expected. My first thought is to change NSMethodSignature to "d @:" (d is double)

The result is displayed both on the device and on the simulator, but there is something strange on the simulator. Run this code and it will work on the 6th loop with some exception 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 am interested in two questions: why does NaN return to the emulator in the first example, and what happened in the second example?

+6
source share
2 answers

For your first question, this is what I found on the simulator

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

Thus, instead of 0.0, NAN will be displayed (fff8000000000000).

To take a deeper look at what is different between [NSMethodSignature signatureWithObjCTypes:" d@ :"] and [NSMethodSignature signatureWithObjCTypes:" Q@ :"] see this

 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 that the second method signature has flags {isFloat} on the return value. I am not an expert on x86 and AMR and low-level ObjC runtime. But I think that the CPU used this flag to determine the type of return value. Without installing it on the x86 CPU, the expected return value of the float is interpreted as NAN.


For your second question, I think this is due to the fact that you will inform at runtime that it will return a 64-bit size value, and thus, the 64-bit memory on the stack is reset. However, the caller expects a 32-bit return size (NSInteger). Therefore, some kind of stack flow occurs and leads to a failure.


I really implemented something similar, trying to make NSNull work like 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]; } 
+2
source

If you want to use message forwarding so that any unimplemented getter method returns 0, instead of throwing an exception, throw an exception, maybe you can use + resolInstanceMethod?

Here is an example of returning an NSString. You will need to configure it in order to return the primitive. let me know if you have a problem.

If you use ARC, you also need a bridge to 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 type codes. A good way to work them out is to make a real method and then think about it. Here the utility returns type codes for the (real) selector in the class: https://github.com/jasperblues/spring-objective-c/blob/master/Source/ ... - user404201 6 minutes ago

+1
source

All Articles