I got an exception that says:
-[NSNull objectForKeyedSubscript:]: unrecognized selector sent to instance
Is it saying I am trying to access an NSNull object with a key?
Any idea what causes this and how to fix it or debug further?
The way to fix it is to not attempt objectForKeyedSubscript on an NSNull object. (I'm betting you're handling some JSON data and aren't prepared for a NULL value.)
(And apparently objectForKeyedSubscript is what the new array[x] notation translates into.)
(Note that you can test for NSNull by simply comparing with == to [NSNull null], since there's one and only one NSNull object in the app.)
What ever value you are storing, despite what the editor tells you, at run time you are storing an NSNull, and later on trying to call objectForKeyedSubscript. I am guessing this happening on what is expected to be an NSDictionary. Some thing like:
NSString *str = dict[#"SomeKey"]
Either a piece of code beforehand is not doing its job and investigate there, or perform some validation:
NSDictionary *dict = ...;
if ( [dict isKindOfClass:[NSDictionary class]] ) {
// handle the dictionary
}
else {
// some kind of error, handle appropriately
}
I often have this kind of scenario when dealing with error messages from networking operations.
I suggest adding a category to NSNull to handle this in the same way you would expect a subscript call to be handled if it it were sent to nil.
#implementation NSNull (Additions)
- (NSObject*)objectForKeyedSubscript:(id<NSCopying>)key {
return nil;
}
- (NSObject*)objectAtIndexedSubscript:(NSUInteger)idx {
return nil;
}
#end
A simple way to test is like this:
id n = [NSNull null];
n[#""];
n[0];
With this category, this test should be handled successfully/softly.
Related
This is probably impossible with a category, but just in case it is doable, I wanted to find out.
I wrote a category on NSString and I have a category method that parses a comma delimited string into an NSArray cleaning up extra commas and spaces, and trimming the ends... so more comprehensive than the built-in method of similar functionality. Here's the rub though... what if the string is nil? Is there any way I can return an initialized NSArray with 0 objects instead of returning nil?
The scenario would go something like this...
NSArray *parsed = [someString parseStringIntoArray];
... assume someString is nil. Can I somehow get an initialized array out of this?
Obviously there are ways to work AROUND this, but keeping it clean and succinct, and using the category method... is it possible?
No. But yes, if you make some changes:
Since you call a instance method, this won't work. When you send a message to nil (aka call a method on a nil object) you will always get nil.
This is the key concept of nil itself.
You can for example add a class method and in this class method, you can then test against nil and return an empty array instead:
+ (NSArray *)parseStringIntoArray:(NSString *)string {
return [string componentsSeparatedByString:#","] ?: #[];
}
or you can simply use, what NSString has built in:
NSArray *parts = [#"foo,bar,la,le,lu" componentsSeparatedByString:#","] ?: #[];
EDIT
No, there's no way to return anything from a message sent to nil. That is baked into the very core of the runtime: objc_msgSend() is responsible for this behavior.
If the receiver is nil, objc_msgSend() resolves the expression to the appropriate 0 value for the return type of the method.
You will have to test for nil either before or after and change the value of the array manually.
(Incidentally, the fact that this is a category method is irrelevant.)
I have the following bit of code where I'm getting a response from the server and trying to parse out error messages.
I notice that on occasion the message object isn't returning as type NSDictionary and will crash the application. I'm wondering what the best practice is to protect against that? In general I try to avoid doing instanceof checks. Likewise has selector checks. It feels like there should be a better way to do this than explicitly check I'm allowed to be using those methods / getting back type that I expect.
NSDictionary *message = [serverErrorJSON objectForKey:#"message"];
if (message != nil) {
return [message objectForKey:#"form"];
}
if ([serverErrorJSON isKindOfClass:NSDictionary.class]) {
return serverErrorJSON[#"message"][#"form"];
}
return nil;
Its good to use the literal syntax both syntactically and programatically.
You don't have to chain them together either, you might want to use another property of the JSON,
if ([serverErrorJSON isKindOfClass:NSDictionary.class]) {
NSDictionary *message = serverErrorJSON[#"message"];
//...
return message[#"form"];
}
return nil;
Your code:
NSDictionary *message = [serverErrorJSON objectForKey:#"message"];
if (message != nil) {
return [message objectForKey:#"form"];
}
The reason you are getting "unrecognized selector" here is that [serverErrorJSON objectForKey:#"message"] is not returning an NSDictionary as you expect. Since you are calling objectForKey: on that object, the recommended way to handle this is to wrap that call with respondsToSelector: :
id message = nil;
if ([serverErrorJSON respondsToSelector:#selector(objectForKey:)]){
message = [serverErrorJSON objectForKey:#"message"];
if ([message respondsToSelector:#selector(objectForKey:)]){
return [message objectForKey:#"form"];
}
}
This tests for the presence of the selector (method) you are calling, and is safer and more compatible than using isKindOfClass: and the like. It's always better to test for capabilities rather than class names, etc. You don't care if the object is an NSDictionary, you care if it can provide an object for a key using the method signature you are invoking.
The best way to protect against unrecognized selector errors is to check respondsToSelector or isKindOfClass depending on your use case.
Unfortunately your code can get pretty cluttered if you find yourself needing to verify that an object is a dictionary frequently, which can occur in your situation like yours with nested dictionaries in the data structure.
You can clean things up by adding a category:
#interface NSDictionary (Safe)
//Returns objectForKey if dictionary param is valid, else returns nil
+ (id) _safeObjectForKey: (NSString*) key
dict: (NSDictionary*) dictionary;
#end
#implementation NSDictionary (Safe)
+ (id) _safeObjectForKey:(NSString *)key
dict:(NSDictionary *)dictionary {
if ([dictionary isKindOfClass:[NSDictionary class]]) {
return [dictionary objectForKey:key];
}
return nil;
}
#end
and then to use it, given your example:
NSDictionary *message = [NSDictionary _safeObjectForKey: #"message"
dict: serverErrorJSON];
return [NSDictionary _safeObjectForKey:#"form"
dict:message];
You can do something similar for other common unrecognized selector error generators as well, like NSArray's objectAtIndex etc.
I am doing something like this:
// GET THE USER ID
NSUserDefaults *standardUserDefaults = [NSUserDefaults standardUserDefaults];
NSString *user_id = [standardUserDefaults objectForKey:#"user_id"];
And then checking whether the user_id is empty
if ([user_id length] == 0) {
proceed = false;
NSLog(#"Error: User id is not set.");
}
And I get this runtime exception:
* Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSCFBoolean length]: unrecognized selector sent to instance 0x1469f70'
Any idea why I get the exception? I didn't think there was anything too wrong with what I was doing.
Thanks!
This:
NSString *user_id = [standardUserDefaults objectForKey:#"user_id"];
Is returning an NSNumber (as NSCFBoolean is a private subclass of NSNumber) rather than a string. It therefore doesn't implement length, causing the exception.
Perhaps you want to test [user_id intValue] > 0? Even if you convert it to a string it'll always have some length.
(side issues raised: merely declaring user_id as a reference to an NSString doesn't mean that anything you assign to it magically becomes a string; indeed there are no type object-type coercion effects whatsoever. The compiler doesn't complain because the NSUserDefaults return objects of type id, i.e. it guarantees they're objects but makes no claims as to their type, and the compiler doesn't know either. All objects may be cast to and from id without generating a warning, so that it can be used by classes such as the user defaults, NSArray, etc, where they can accept anything as long as it's an object).
EDIT: based on issues raised in the comments, it sounds like the thing originally being stored may not be a string. A good way to validate web stuff is probably something like:
// fall through here if the object isn't a string
if(![responseString isKindOfClass:[NSString class]])
{
// check whether it's something that can be converted to a string
if(![responseString respondsToSelector:#selector(stringValue)])
{
// some sort of error condition; the server returned something
// that isn't a string and doesn't convert (in and of itself) to string
}
else
{
// convert the object to a string
responseString = [responseString stringValue];
}
}
The reason you are getting that error is you are trying to call 'length' on what appears to be a boolean. Either way, for checking if a string is blank here are some easy methods you can add to the NSString class by means of a category:
-(BOOL)isBlank{
return [[self trim] length]==0;
}
-(NSString *)trim{
return [self stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet] ];
}
Then to call it it's just:
[myString isBlank];
so i make a lot of server calls for my app. what is returned can depend on the result of the server operation.
say i make an api call to "foo" which will return either a hash map/nsdictionary if successful or a bool (or a 0 for false, meaning it did not execute).
with my code, i typecast it to i believe it should be assuming it was a successful operation. i will check to see if i get back something else then i expected, say a BOOL false.
NSString *mapContext = (NSString *) [call xmlrpcCall:#"load_map" withObjects: [NSArray arrayWithObjects:dataCenter.state,nil]];
NSLog(#"mapContext in loadStateMap: %#", mapContext);
if ([mapContext isKindOfClass:[NSDictionary class]])
{
if ([mapContext objectForKey:#"faultCode"])
{
NSLog(#"mapContext: %#", mapContext);
[self defaultAlert:mapContext titleMsg:#"load_map"];
}
}
here i ask the server to load a map. if successfull, it will return a string. if it fails, it will return a dictionary with a fault code and a fault message. since mapContext is instantiated as a string, when i check to see if its a dictionary and check for a key fault code, xcode gives me a warning that mapContext may not respond to "objectForKey". i understand completely why i get the warning, but is there a way i can prevent the warning? it never breaks the app but its annoying to see 30+ warnings about this issue.
Use id, this is what it is for and that is why so many abstracted foundation classes use them (NSArray anyone).
//problem solved!
id mapContext = [call xmlrpcCall:#"load_map" withObjects: [NSArray arrayWithObjects:dataCenter.state,nil]];
in my code, I need to compare two strings to see if they are equal. if they are it needs to preform a function. one of the strings is just a #"someString", the other is part of an object.
if ([[[mine metal] stringValue] isEqualToString:#"Gold"])
{
//some function
}
however there are some complications when I do this. first, it gives me a warning: NSString may not respond to -stringValue. and when I run the Application it quits out at the if statement: the console reports " -[NSCFString stringValue] : unrecognized selector sent to instance." mine.metal is defined through a fast enumeration loop across an array; the metal attribute is defined as an NSString, and NSLog is able to display this string. what else am I missing?
The compiler warning and the subsequent run-time error both tell you what the problem is.
[mine metal] returns an NSString. NSString doesn't have a method called stringValue.
If [mine metal] does indeed return an NSString then you can do this:
if ([[mine metal] isEqualToString:#"Gold"])
{
//some function
}