I'm developing an iOS 5.0+ app with latest SDK.
I get a very strange error with this code:
- (NSMutableURLRequest*)setupRequestWithService:(NSString*)service andMethod:(NSString*)method
{
NSString* url = [NSString stringWithFormat:#"%#%#.svc/%#", serverUrl, service, method];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
// Set authentication token.
NSLog(#"???????????? %#", authenticationToken);
if (authenticationToken == nil)
NSLog(#"NULL AUTHTOKEN");
if ([authenticationToken isEqual:[NSNull null]])
NSLog(#"NSNULL AUTHTOKEN");
if (request == nil)
NSLog(#"NULL REQUEST");
[request addValue:authenticationToken forHTTPHeaderField:REQUEST_HEADER_AUTH_TOKEN];
return request;
}
This is my log:
???????????? <null>
NSNULL AUTHTOKEN
-[NSNull length]: unrecognized selector sent to instance 0x3b5a5090
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull length]: unrecognized selector sent to instance 0x3b5a5090'
It seems that authenticationToken is NULL. But I don't understand that, if authenticationToken is NULL why I don't see NULL AUTHTOKEN on the log.
I get this error the second time I run that method, the first time, I don't get any error. This is my log:
???????????? (null)
NULL AUTHTOKEN
By the way:
NSString* authenticationToken;
Any advice?
Maybe there is a Memory Leak somewhere...
My solution to this maddening use of NSNull by JSON interpreters is to create a category on NSNull, where I define integerValue, floatValue, length, etc - return 0 for all. Everytime you get another crash add a new category. I think I had 6 or 7 when I had this issue.
The problem with NOT doing this is you have to look for the NULL everywhere in your converted objects - a PITA in my opinion.
EDIT: the code I'm using, all in a NSNull+JSON.m file:
#interface NSNull (JSON)
#end
#implementation NSNull (JSON)
- (NSUInteger)length { return 0; }
- (NSInteger)integerValue { return 0; };
- (float)floatValue { return 0; };
- (NSString *)description { return #"0(NSNull)"; }
- (NSArray *)componentsSeparatedByString:(NSString *)separator { return #[]; }
- (id)objectForKey:(id)key { return nil; }
- (BOOL)boolValue { return NO; }
#end
EDIT2: Now in Swift 3:
extension NSNull {
func length() -> Int { return 0 }
func integerValue() -> Int { return 0 }
func floatValue() -> Float { return 0 };
open override var description: String { return "0(NSNull)" }
func componentsSeparatedByString(separator: String) -> [AnyObject] { return [AnyObject]() }
func objectForKey(key: AnyObject) -> AnyObject? { return nil }
func boolValue() -> Bool { return false }
}
The error message is pretty clear. NSNull and nil are different things:
The NSNull class defines a singleton object used to represent null values in
collection objects (which don’t allow nil values).
If you want to check if authenticationToken is NSNull try: [authenticationToken isEqual: [NSNull null]]
In line with David H's answer, how about a category on NSNull that just uses ObjC's message forwarding to "do nothing", to emulate the runtime's behavior when sending messages to nil?
Like this:
#interface NSNull (ForwardInvocation)
#end
#implementation NSNull (ForwardInvocation)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSNull methodSignatureForSelector:#selector(description)];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// do nothing; prevent 'unrecognized selector' crashes
}
#end
The [NSNull methodSignatureForSelector:#selector(description)]; takes advantage of the fact that NSNull inherits from NSObject, which provides the description method. This satisfies the forwarding mechanism requirement for implementing -methodSignatureForSelector:.
The problem comes because your method return an NSNull object.
You can't check [authenticationToken isEqual:[NSNull null]]) because [NSNull null] give an instance of an object. So it's different from your object itself.
If you want to check if you received an NSNull object you need to check like this: [authenticationToken isKindOfClass:[NSNull class]] instead.
Related
I'm developing an iOS 5.0+ app with latest SDK.
I get a very strange error with this code:
- (NSMutableURLRequest*)setupRequestWithService:(NSString*)service andMethod:(NSString*)method
{
NSString* url = [NSString stringWithFormat:#"%#%#.svc/%#", serverUrl, service, method];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
// Set authentication token.
NSLog(#"???????????? %#", authenticationToken);
if (authenticationToken == nil)
NSLog(#"NULL AUTHTOKEN");
if ([authenticationToken isEqual:[NSNull null]])
NSLog(#"NSNULL AUTHTOKEN");
if (request == nil)
NSLog(#"NULL REQUEST");
[request addValue:authenticationToken forHTTPHeaderField:REQUEST_HEADER_AUTH_TOKEN];
return request;
}
This is my log:
???????????? <null>
NSNULL AUTHTOKEN
-[NSNull length]: unrecognized selector sent to instance 0x3b5a5090
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull length]: unrecognized selector sent to instance 0x3b5a5090'
It seems that authenticationToken is NULL. But I don't understand that, if authenticationToken is NULL why I don't see NULL AUTHTOKEN on the log.
I get this error the second time I run that method, the first time, I don't get any error. This is my log:
???????????? (null)
NULL AUTHTOKEN
By the way:
NSString* authenticationToken;
Any advice?
Maybe there is a Memory Leak somewhere...
My solution to this maddening use of NSNull by JSON interpreters is to create a category on NSNull, where I define integerValue, floatValue, length, etc - return 0 for all. Everytime you get another crash add a new category. I think I had 6 or 7 when I had this issue.
The problem with NOT doing this is you have to look for the NULL everywhere in your converted objects - a PITA in my opinion.
EDIT: the code I'm using, all in a NSNull+JSON.m file:
#interface NSNull (JSON)
#end
#implementation NSNull (JSON)
- (NSUInteger)length { return 0; }
- (NSInteger)integerValue { return 0; };
- (float)floatValue { return 0; };
- (NSString *)description { return #"0(NSNull)"; }
- (NSArray *)componentsSeparatedByString:(NSString *)separator { return #[]; }
- (id)objectForKey:(id)key { return nil; }
- (BOOL)boolValue { return NO; }
#end
EDIT2: Now in Swift 3:
extension NSNull {
func length() -> Int { return 0 }
func integerValue() -> Int { return 0 }
func floatValue() -> Float { return 0 };
open override var description: String { return "0(NSNull)" }
func componentsSeparatedByString(separator: String) -> [AnyObject] { return [AnyObject]() }
func objectForKey(key: AnyObject) -> AnyObject? { return nil }
func boolValue() -> Bool { return false }
}
The error message is pretty clear. NSNull and nil are different things:
The NSNull class defines a singleton object used to represent null values in
collection objects (which don’t allow nil values).
If you want to check if authenticationToken is NSNull try: [authenticationToken isEqual: [NSNull null]]
In line with David H's answer, how about a category on NSNull that just uses ObjC's message forwarding to "do nothing", to emulate the runtime's behavior when sending messages to nil?
Like this:
#interface NSNull (ForwardInvocation)
#end
#implementation NSNull (ForwardInvocation)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSNull methodSignatureForSelector:#selector(description)];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// do nothing; prevent 'unrecognized selector' crashes
}
#end
The [NSNull methodSignatureForSelector:#selector(description)]; takes advantage of the fact that NSNull inherits from NSObject, which provides the description method. This satisfies the forwarding mechanism requirement for implementing -methodSignatureForSelector:.
The problem comes because your method return an NSNull object.
You can't check [authenticationToken isEqual:[NSNull null]]) because [NSNull null] give an instance of an object. So it's different from your object itself.
If you want to check if you received an NSNull object you need to check like this: [authenticationToken isKindOfClass:[NSNull class]] instead.
I'm getting Json response with subArrays of dictionaries and I'm looking for particular value inside of one the subArrays. This is the subArray I care about:
<__NSArrayM 0x600000047680>(
{
value = "VeryImportanValue";
},
{
value = "someValue";
},
{
value = 0912131235235234;
}
)
I'm looking for the dictionary with the value of "VeryImportanValue". But as you can see you of the values are numeric and I check:
if ([[dict objectForKey:#"value"]isEqualToString:#"VeryImportanValue"])
I get this error:
-[__NSCFNumber isEqualToString:]: unrecognized selector sent to instance
But switch the if statement to fix the error above:
if ([[[dict objectForKey:#"value"] stringValue] isEqualToString:#"VeryImportanValue"])
I get this error:
-[__NSCFString stringValue]: unrecognized selector sent to instance
How can I check the value of the dictionary without getting any of this errors regarless if is string or numeric value?
I'll really appreciate your help.
You could create a method that turns the unknown object into a string:
- (NSString *)stringForStringOrNumber:(id)object
{
NSString *result = nil;
if ([object isKindOfClass:[NSString class]]) {
result = object;
} else if ([object isKindOfClass:[NSNumber class]]) {
result = [object stringValue];
} else {
result = #"I can't guess";
}
return result;
}
Before you do isEqualToString, you should first check the value type of your dictionary.
if(dict[#"value"]isKindOfClass==[NSString class]) //value is string
A JSON API feed used by our iOS ObjectiveC app is a bit flaky, so sometimes a field is null.
When parsing JSON we use
NSDictionary *json = [self JSONFromResponseObject:responseObject];
Then try to use the fields with e.g.
[widgetIDArray addObject:widget[#"name"][#"id"]];
Where sometimes the "name" field will be a null. Do we:
1) Ask the API provider to clean up their flaky API code
2) Check for null each and every time we try to use something from the json dict
if ( ![widget[#"name"] isKindOfClass:[NSNull class]] )
3) Use try - catch
#try {
[widgetIDArray addObject:widget[#"name"][#"id"]];
}
#catch (NSException *exception)
{
NSLog(#"Exception %#",exception);
}
ANSWER:
Thanks for the answers, below. Here is the extension to NSObject I added that allows me to get deeply nested JSON items that may or may not be present.
First call with something like
self.item_logo = [self valueFromJSONWithKeyArray:event withKeyArray:#[#"categories",#"bikes",#"wheels",#"model",#"badge_uri"]];
Here is the code in NSObject+extensions.m
- (id) valueFromJSONWithKeyArray:(id)json withKeyArray:(NSArray *)keyArray
{
for (NSString * keyString in keyArray)
{
if ([json[keyString] isKindOfClass:[NSObject class]])
{
json = json[keyString]; // go down a level
}
else
{
return nil; // we didn't find this key
}
}
return json; // We successfully found all the keys, return the object
}
null in a JSON response isn't "flaky", it is absolutely standard.
Even if it was "flaky", any message that you receive from the outside is an attack vector that could allow an attacker to hack into your program, so resilience is required. Crashing when your receive a null allows a DOS attack against your application.
#try / #catch is awful. Exceptions are thrown in response to programming errors. You don't catch them, you fix your code.
How do you fix your code? Simple. Write a few helper methods in an NSDictionary extension.
First you don't know that json is a dictionary. So you add an NSDictionary class method where you pass in anything and it returns what you passed if it is a dictionary and nil (with appropriate logging) if it is anything else.
Next you assume that there is a dictionary under the key "name". So you write an extension "jsonDictionaryForKey" which returns a dictionary if there is one, and nil (with appropriate logging) if it is anything else.
And so on. Make your JSON parsing bullet proof if you want to call yourself a professional developer. For extra bonus points you add a method which will take a dictionary and list all keys that are present that you didn't ask for - so you know if your API is sending things that you don't expect.
You can delete all NSNULL values in your JSON object. Here is a function I used in my library to git rid of all null values in a JSON object.
id BWJSONObjectByRemovingKeysWithNullValues(id json, NSJSONReadingOptions options) {
if ([json isKindOfClass:[NSArray class]]) {
NSMutableArray *mutableArray = [NSMutableArray arrayWithCapacity:[(NSArray *)json count]];
for (id value in (NSArray *)json) {
[mutableArray addObject:BWJSONObjectByRemovingKeysWithNullValues(value, options)];
}
return (options & NSJSONReadingMutableContainers) ? mutableArray : [NSArray arrayWithArray:mutableArray];
} else if ([json isKindOfClass:[NSDictionary class]]) {
NSMutableDictionary *mutableDictionary = [NSMutableDictionary dictionaryWithDictionary:json];
for (id<NSCopying> key in [(NSDictionary *)json allKeys]) {
id value = [(NSDictionary *)json objectForKey:key];
if (isNullValue(value)) {
[mutableDictionary removeObjectForKey:key];
} else if ([value isKindOfClass:[NSArray class]] || [value isKindOfClass:[NSDictionary class]]) {
[mutableDictionary setObject:BWJSONObjectByRemovingKeysWithNullValues(value, options) forKey:key];
}
}
return (options & NSJSONReadingMutableContainers) ? mutableDictionary : [NSDictionary dictionaryWithDictionary:mutableDictionary];
}
return json;
}
After all null values have been cleared, perhaps the exceptions will gone too.
An approach that's often used is categories similar to these on NSDictionary and NSMutableDictionary, that ignore nil and NSNull.
#interface NSDictionary (nullnilsafe)
- (ObjectType)nullnilsafe_objectForKey:(KeyType)aKey;
#end
#implementation NSDictionary
- (ObjectType)nullnilsafe_objectForKey:(KeyType)aKey
{
id obj = [self objectForKey:aKey];
if (obj && ![obj isKindOfClass:[NSNull class]] ){
return obj;
}
return nil;
}
#end
#interface NSMutalbeDictionary (nullnilsafe)
- (void)nullnilsafe_setObject:(ObjectType)anObject forKey:(id<NSCopying>)aKey;
#end
#implementation NSMutalbeDictionary
- (void)nullnilsafe_setObject:(ObjectType)anObject forKey:(id<NSCopying>)aKey
{
if (anObject && aKey && ![anObject isKindOfClass:[NSNull class]]){
[self setObject:anObject forKey:aKey];
}
}
#end
Trying to check for validity of the data in item
(item is NSDictionary)
I thought this should work but I do get into the second if and crash with:
unrecognized selector sent to instance
becuase galleryArr is (null)
NSArray *galleryArr = [item objectForKey:#"photos"];
if (galleryArr != nil ) {
if ([galleryArr count] != 0) {
//do something
}
}
Any ideas?
I've solved this issue with a simple this simple Objective-C category:
NSDictionary+NotNull.h
#import <Foundation/Foundation.h>
/*! This category extends NSDictionary to work around an issue with NSNull object.
*/
#interface NSDictionary (NotNull)
/*! #abstract Returns the value associated with a given key, but only if the value is not NSNull.
#param aKey The key for which to return the corresponding value.
#return The value associated with the given key, or nil if no value is associated with the key, or the value is NSNull.
*/
- (id)objectOrNilForKey:(id)aKey;
#end
NSDictionary+NotNull.m
#import "NSDictionary+NotNull.h"
#implementation NSDictionary (NotNull)
- (id)objectOrNilForKey:(id)aKey
{
id object = [self objectForKey:aKey];
if (object == [NSNull null]) {
return nil;
}
return object;
}
#end
Now you can just call:
NSArray *galleryArr = [item objectOrNilForKey:#"photos"];
Add a check for [gallryArr isKindOfClass:[NSArray class]].
Maybe you get NSNull? That's a singleton ([NSNull null]) object that represent nil. You can check if([gallryArr isKindOfClass:[NSArray class]]).
I'm developing an iOS 5.0+ app with latest SDK.
I get a very strange error with this code:
- (NSMutableURLRequest*)setupRequestWithService:(NSString*)service andMethod:(NSString*)method
{
NSString* url = [NSString stringWithFormat:#"%#%#.svc/%#", serverUrl, service, method];
NSMutableURLRequest* request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
// Set authentication token.
NSLog(#"???????????? %#", authenticationToken);
if (authenticationToken == nil)
NSLog(#"NULL AUTHTOKEN");
if ([authenticationToken isEqual:[NSNull null]])
NSLog(#"NSNULL AUTHTOKEN");
if (request == nil)
NSLog(#"NULL REQUEST");
[request addValue:authenticationToken forHTTPHeaderField:REQUEST_HEADER_AUTH_TOKEN];
return request;
}
This is my log:
???????????? <null>
NSNULL AUTHTOKEN
-[NSNull length]: unrecognized selector sent to instance 0x3b5a5090
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull length]: unrecognized selector sent to instance 0x3b5a5090'
It seems that authenticationToken is NULL. But I don't understand that, if authenticationToken is NULL why I don't see NULL AUTHTOKEN on the log.
I get this error the second time I run that method, the first time, I don't get any error. This is my log:
???????????? (null)
NULL AUTHTOKEN
By the way:
NSString* authenticationToken;
Any advice?
Maybe there is a Memory Leak somewhere...
My solution to this maddening use of NSNull by JSON interpreters is to create a category on NSNull, where I define integerValue, floatValue, length, etc - return 0 for all. Everytime you get another crash add a new category. I think I had 6 or 7 when I had this issue.
The problem with NOT doing this is you have to look for the NULL everywhere in your converted objects - a PITA in my opinion.
EDIT: the code I'm using, all in a NSNull+JSON.m file:
#interface NSNull (JSON)
#end
#implementation NSNull (JSON)
- (NSUInteger)length { return 0; }
- (NSInteger)integerValue { return 0; };
- (float)floatValue { return 0; };
- (NSString *)description { return #"0(NSNull)"; }
- (NSArray *)componentsSeparatedByString:(NSString *)separator { return #[]; }
- (id)objectForKey:(id)key { return nil; }
- (BOOL)boolValue { return NO; }
#end
EDIT2: Now in Swift 3:
extension NSNull {
func length() -> Int { return 0 }
func integerValue() -> Int { return 0 }
func floatValue() -> Float { return 0 };
open override var description: String { return "0(NSNull)" }
func componentsSeparatedByString(separator: String) -> [AnyObject] { return [AnyObject]() }
func objectForKey(key: AnyObject) -> AnyObject? { return nil }
func boolValue() -> Bool { return false }
}
The error message is pretty clear. NSNull and nil are different things:
The NSNull class defines a singleton object used to represent null values in
collection objects (which don’t allow nil values).
If you want to check if authenticationToken is NSNull try: [authenticationToken isEqual: [NSNull null]]
In line with David H's answer, how about a category on NSNull that just uses ObjC's message forwarding to "do nothing", to emulate the runtime's behavior when sending messages to nil?
Like this:
#interface NSNull (ForwardInvocation)
#end
#implementation NSNull (ForwardInvocation)
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
return [NSNull methodSignatureForSelector:#selector(description)];
}
- (void)forwardInvocation:(NSInvocation *)anInvocation {
// do nothing; prevent 'unrecognized selector' crashes
}
#end
The [NSNull methodSignatureForSelector:#selector(description)]; takes advantage of the fact that NSNull inherits from NSObject, which provides the description method. This satisfies the forwarding mechanism requirement for implementing -methodSignatureForSelector:.
The problem comes because your method return an NSNull object.
You can't check [authenticationToken isEqual:[NSNull null]]) because [NSNull null] give an instance of an object. So it's different from your object itself.
If you want to check if you received an NSNull object you need to check like this: [authenticationToken isKindOfClass:[NSNull class]] instead.