1

I ran into some old code in a project and I do not understand why it does not crash.

Data is being ingested from an HTTP request to an API and certain values are returned as strings, but treated as NSNumbers. For example, in the code below, value is an NSString.

[NSDecimalNumber decimalNumberWithDecimal:[(NSNumber *)value decimalValue]]

However, it is incorrectly cast to an NSNumber and the decimalValue method of NSNumber is called. There is no decimalValue method for NSString, so I don't understand why the line above does not cause a crash. When I inspect value with [value isKindOfClass:[NSString class]] and [value isKindOfClass:[NSNumber class]] I get the expected results.

Is there a behavior of the runtime I do not understand? Does a cast cause the methods of a different class to be used?

edit I'm updating the title of this question to more accurately reflect the issue. The answer was that NSString has a private decimalValue method that was causing the confusion.

Alex
  • 1,625
  • 12
  • 18
  • Does NSNumber have a `decimalValue` method? (Remember, Objective-c is "duck typed".) – Hot Licks Sep 02 '15 at 00:51
  • @matt -- What??? https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSDecimalNumber_Class/index.html#//apple_ref/occ/instp/NSDecimalNumber/decimalValue – Hot Licks Sep 02 '15 at 00:52
  • I just ran a quick test where I added a `castTest` method to one of the classes in my project. I then added `id something = @"a string";` followed by `[(MyClass *)something castTest]`. This did cause `unrecognized selector sent to instance`, so I'm really not sure why the example in my original question does not crash. – Alex Sep 02 '15 at 01:19
  • 2
    It sounds like your project might have a category on `NSNumber` that adds a `decimalValue` method. – rob mayoff Sep 02 '15 at 01:20
  • @robmayoff Thanks for the suggestion. However, I have searched the project and "decimalValue" only appears on the few lines matching my original example. – Alex Sep 02 '15 at 01:22
  • @matt - Yeah. Apple had mucked up the documentation index yet again, so it's damn near impossible to find the class you're after. I thought I had it but didn't look close enough. – Hot Licks Sep 02 '15 at 01:26
  • Let's see if this link works: https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSNumber_Class/index.html#//apple_ref/occ/instp/NSNumber/decimalValue – Hot Licks Sep 02 '15 at 01:27
  • Either you are wrong about `value` being an NSString (in this instance), or there is a category on NSString. – Hot Licks Sep 02 '15 at 01:28
  • 1
    @HotLicks there is a category, it is private however. It's `NSString(NSDecimalExtensions)` IIRC, and it's in Foundation. I wouldn't depend on it existing for any production code, but it's definitely there if you look at the symbols exported by Foundation. – Richard J. Ross III Sep 02 '15 at 03:47
  • @RichardJ.RossIII Thanks for pointing down the exact private category. I created a brand new project and put `[(NSNumber *)@"asdf" decimalValue];` in `didFinishLaunchingWithOptions` and it did not crash. This got me thinking there might be a private method used by `[NSDecimalNumber initWithString:]`. I printed all methods using the technique described in this question http://stackoverflow.com/questions/2094702/get-all-methods-of-an-objective-c-class-or-instance and saw that NSString does indeed have a method named `decimalValue`. – Alex Sep 02 '15 at 04:18
  • @RichardJ.RossIII If you submit your comment as an answer I will accept it, so you are properly credited. – Alex Sep 02 '15 at 19:27

1 Answers1

1

The real answer? Voodoo. Not really, but sort of.

Lots of classes in cocoa have private methods behind the scenes. Usually, these methods go mostly unnoticed, and even more rarely used.

However, if you grab their implementation using class_getMethodImplementation and use dladdr to print the location of the symbol as such:

IMP theImp = class_getMethodImplementation([NSString class], @selector(decimalValue));
Dl_info info;
dladdr(theImp, &info);

puts(info.dli_sname);

You should see something like -[NSString(NSDecimalExtension) decimalValue].

My best guess is that this method is used for KVO & friends when marshaling values to/from decimal values, or it's used with NSNumberFormatter.

Richard J. Ross III
  • 55,009
  • 24
  • 135
  • 201