Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Under what conditions might instancesRespondToSelector: return true, but performSelector: throw an exception

I have code distributed in a library which looks like this:

if ([[NSString class] instancesRespondToSelector: @selector(JSONValue)]) {
  NSString *jsonString = [[[NSString alloc] initWithData: jsonData encoding: NSUTF8StringEncoding] autorelease];
  dict = [jsonString performSelector: @selector(JSONValue)];
}

For some reason a -[__NSCFString JSONValue]: unrecognized selector sent to instance exception is getting thrown when the performSelector: method gets called. This is code that is distributed in a library that I wrote, but I can't reproduce or debug it myself. Instead a third-party is reporting this problem. Under what conditions could instancesRespondToSelector: while actually calling the method using performSelector: throw an exception?

edit There is a case which could explain why this occurs, but it doesn't make sense. If the developers were to do something like this:

@implementation NSString (OurHappyCategory)

+ (BOOL)instancesRespondToSelector:(SEL)aSelector
{
  return YES;
}

@end

It would explain why the code is executing, but it would of course be a very bad thing to do. Is there a way this problem could occur that makes sense?

like image 246
ThomasW Avatar asked Apr 09 '13 07:04

ThomasW


3 Answers

NSString is basically a class cluster, and brings up all sorts of complications... you actually need to ask the instance if it responds to the selector.

NSString *jsonString = [[[NSString alloc] initWithData: jsonData encoding: NSUTF8StringEncoding] autorelease];
if ([jsonString respondsToSelector: @selector(JSONValue)]) {
  dict = [jsonString performSelector: @selector(JSONValue)];
}

but your problem probably has to do with compiling or linking the extension... if the category is added in a library then you will need to sprinkle in the -ObjC linker flag.

EDIT:
I have been working a little bit on reproducing this issue... which I am unable to... do you have any more information.. for example is the failure only occurring on the simulator, or just on device, iOS 4.x, GNU linker vs LLDB's linker, ABI/Runtime differences?

like image 90
Grady Player Avatar answered Oct 07 '22 16:10

Grady Player


I guess that you didn't import a third party library in a correct way. Usually this methods are added as category to NSString, it happened to me that I could see the .h file but the .m wasn't compiled. You can check it inside xcode target-->build phases-->compile sources. Or check if you aheve this flag inside Project-->Build Settings-->Other linker flag = -all_load

like image 34
Andrea Avatar answered Oct 07 '22 17:10

Andrea


The exception does not come directly from the Objective-C runtime in response to sending a message that the instance doesn't recognize. It comes from -[NSObject doesNotRecognizeSelector:], which is invoked at the end of various method lookup and forwarding mechanisms.

However, anything can invoke -doesNotRecognizeSelector: when it wants. A class can "disavow" an inherited method by overriding it and making the override just invoke -doesNotRecognizeSelector:. As documented, this will result in the exception you're seeing in spite of the fact that -respondsToSelector: (and +instancesRespondToSelector:) returning YES.

I couldn't tell you why __NSCFString is doing that in your end user's case. Is the app which is using your library using categories or method swizzling to modify the methods on that class?

Also, does your logging show the actual stack trace capture in the exception? That might be informative.

like image 39
Ken Thomases Avatar answered Oct 07 '22 17:10

Ken Thomases