I have the following code in a loop
NSArray * images = [definitionDict objectForKey:#"Images"];
NSLog(#"images in definitionDict %#", images);
if (!images )
NSLog(#"NULL");
else
NSLog(#"NOTNULL");
which gives the following outputs
images in definitionDict (
"/some/brol/brol.jpg"
)
NOTNULL
images in definitionDict <null>
NOTNULL
I do not understand the second case, where the images array is null. Why is this not detected correctly in my test ? How can I debug such a problem ?
<null> is not nil. nil will print (null) when printed. What you have is an NSNull. NSNull IS an object, it just doesn't respond to much. Its available as a placeholder for you to use.
To test for NSNull you can use if ([images isEqual:[NSNull null]])
See the docs for more info on NSNull
If you want to print out the memory address of an Objective-C object, or any other pointer, you should use the flag %p not %#. The flag %#, expects a string.
However if the argument isn't a string, NSLog will automatically call -description on the passed object. And when the method returns an NSNullobject, the -descriptionon that object returns the string <null>
NSObject *o = nil;
NSLog(#"%p", o);
Output: 0x00000000
NSObject *o = [[NSObject alloc] init];
NSLog(#"%p", o);
[o release];
Output: something like 0x12345678
Mind:
NSNull *n = [NSNull null];
NSLog(#"%p", n);
Output: a memory address that always will be the same, but will differ from 0x00000000
The correct way to test if their are objects in the array is like this.
NSArray *myArray = [someObject array];
if([myArray isEqual:[NSNull null]]) {
NSLog(#"No objects");
} else {
NSLog(#"%d objects.", (int)[myArray length];
}
Related
I'm getting a crash in my app with this error:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[NSNull length]: unrecognized selector sent to instance 0x1cb8068'
I have a lot of data I need to display in my table.
In this example, I am working with the key, brands, in my JSON.
If I NSLog the brands I will get something like this:
Brand A
Brand B
Brand C
null
Brand E
null
null
Brand Z
When I scroll through my table and I hit a the app crashes.
How can I replace the with a string?
Here's my method:
- (void)updateData
{
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
[manager GET:#"http://mydataurl.com/abc" parameters:params success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray * aArray = [responseObject valueForKeyPath:#"result_payload.items"];
self.inventoryArray = [NSArray arrayWithArray:aArray];
// ***** Brand ******
self.brandArray = [self.inventoryArray valueForKeyPath:#"brand"];
for (int a = 0; a < [self.brandArray count]; a++)
{
self.brandString = [self.brandArray objectAtIndex:a];
}
[self.tableView reloadData];
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
NSLog(#"Error: %#", error);
}];
I have added a category for NSDictionary:
.h
#import <Foundation/Foundation.h>
#interface NSDictionary (Utility)
- (NSString*)stringForKey:(id)key;
#end
.m
#import "NSDictionary+Utility.h"
#implementation NSDictionary (Utility)
- (NSString*)stringForKey:(id)key
{
NSString * string = [self objectForKey:key];
if ([string isEqual:[NSNull null]])
{
return nil;
}
return string;
}
#end
I haven't used categories much, do I need to call this method in a specific place? Or just add the #import to my main class?
Are you sure the JSON is not returning an element with a "null" value?
If you look at the actual JSON document that the server gives you, you will find that it contains "null" values. When there is a "null" value in the data, JSON parsers will produce the value [NSNull null] to represent the null.
You have to check for that value. If you think you've got an NSString, or an NSNumber, or an NSArray or an NSDictionary but what you really have is an [NSNull null], and you send it a message, you will get a crash.
The easiest way to handle this: Add a category to NSDictionary with methods like
(NSString*)stringForKey;
(NSNumber*)numberForKey;
which calls objectForKey, checks the type of the object, and returns nil if its the wrong type.
As an example:
#interface NSDictionary (JSONExtensions)
- (NSString*)stringForKey:(NSString*)key;
#end
#implementation NSDictionary (JSONExtensions)
- (NSString*)stringForKey:(NSString *)key
{
id result = [self objectForKey:key];
if ([result isKindOfClass:[NSNumber class]])
result = [((NSNumber *) result) stringValue];
else if (! [result isKindOfClass:[NSString class]])
result = nil;
return result;
}
#end
This will return nil if the key is not present, or if the value is [NSNull null], or if the value is an array or a dictionary. It will convert a number to a string and return a string, so you are guaranteed to get nil or an NSString*. (Some servers tend to send strings consisting only of digits as numbers instead of strings). So if you want a string for the key "MyString", you write
NSString* myString = [myDict stringForKey:#"MyString"];
and you can be sure the result is nil or a string.
I use
- (id)objectForKeyNotNull:(id)key
{
id object = [self objectForKey:key];
if (object == [NSNull null])
return nil;
return object;
}
to convert the NSNull values to nil. I add this as a category onto NSDictionary.
I have the following in a success block for an AFNetworking getPath call:
+(void)allItemsWithBlock: (void (^)(NSArray *items)) block
{
...
NSMutableArray *mutableItems = [NSMutableArray array];
for (NSDictionary *attributes in [responseObject valueForKey:#"data"]) {
Item *item = [[Item alloc] initWithAttributes:attributes];
[mutableItems addObject:item];
}
NSLog(#"here is a count: %i", [mutableItems count]);
if(block){
block(mutableItems);
}
and in the block that gets passed in, I have the following but get the error listed as a comment:
[Item allItemsWithBlock:^(NSArray *items){
for(Item *thisItem in *items){ // The type 'NSArray' is not a pointer to a fast-enumerable object
NSLog(#"in the block here");
}
}];
I've read up on trying to fast-enumeration but am not sure what the problem is. Is the NSMutableArray -> NSArray an issue? Is it because this array is created in a block and thus could be seen as possibly still 'open for change'? I have seen code like this before in our projects and doesn't seem to be a problem.
thx for any help
This is because NSArray *items is already a pointer to an array, *items is trying to find a pointer to a pointer, which it is not.
Just replace:
for(Item *thisItem in *items){
with:
for(Item *thisItem in items){
I came to notice that executing a for/in operation in objective c on an initialized empty NSMutableArray was not working as expected.
Simplified code is :
+(void) convertArray: (NSMutableArray*)arrayIN {
NSMutableArray *arrayOUT = [NSMutableArray array];
NSLog(#"is nil %d - count %d", !arrayIN, [arrayIN count]);
for(NSObject *o in arrayIN)
[arrayOUT addObject:[o convertToAnotherClass]];
}
Actual code is :
+(BOOL) writeTasks: (NSArray*)tasksArray {
NSMutableArray *arr = [NSMutableArray array];
NSLog(#"is nil %d - count %d", !arr, [arr count]);
for(Task *t in tasksArray)
[arr addObject:[t getDictionary]];
NSError *error;
NSData *jsonData = [NSJSONSerialization dataWithJSONObject:arr options:NSJSONWritingPrettyPrinted error:&error];
if (! jsonData) {
NSLog(#"Got an error: %#", error);
return NO;
} else {
//NSString *jsonString = [[NSString alloc] initWithData:jsonData encoding:NSUTF8StringEncoding];
[jsonData writeToFile:path options:NSDataWritingAtomic error:nil];
return YES;
}
}
the suprising thing is that executing [dummyClass convertArray:[NSMutableArray array]] is showing this :
2012-06-25 13:51:34.236 Planorama[740:707] is nil 0 - count 0
2012-06-25 13:51:34.239 Planorama[740:707] -[__NSArrayM convertToAnotherClass]: unrecognized selector sent to instance 0xde9b580
(lldb)
Why ? arrayIN is empty, why is convertToAnotherClass even called ?
if you use the block based enumeration it will work the way you want.
Also, the output indicates that o is set to some instance of something, so you may have another problem.
Elegant way to get all objects of a specific type in an Objective-C array
Lastly, it looks like this is a static method, but your example calls it as an instance method.
As Joshua Smith pointed out : I am not checking the count of the iterated array. The iterated array was not empty and contained itself because if a mistyped line :
[tasks addObject:tasks]
instead of
[tasks addObject:task]
in a previous method..
Thanks everyone !
PS : the link of Joshua Smith is very useful ! Future readers : check it out !
I'm setting values for properties of my NSManagedObject, these values are coming from a NSDictionary properly serialized from a JSON file. My problem is, that, when some value is [NSNull null], I can't assign directly to the property:
fight.winnerID = [dict objectForKey:#"winner"];
this throws a NSInvalidArgumentException
"winnerID"; desired type = NSString; given type = NSNull; value = <null>;
I could easily check the value for [NSNull null] and assign nil instead:
fight.winnerID = [dict objectForKey:#"winner"] == [NSNull null] ? nil : [dict objectForKey:#"winner"];
But I think this is not elegant and gets messy with lots of properties to set.
Also, this gets harder when dealing with NSNumber properties:
fight.round = [NSNumber numberWithUnsignedInteger:[[dict valueForKey:#"round"] unsignedIntegerValue]]
The NSInvalidArgumentException is now:
[NSNull unsignedIntegerValue]: unrecognized selector sent to instance
In this case I have to treat [dict valueForKey:#"round"] before making an NSUInteger value of it. And the one line solution is gone.
I tried making a #try #catch block, but as soon as the first value is caught, it jumps the whole #try block and the next properties are ignored.
Is there a better way to handle [NSNull null] or perhaps make this entirely different but easier?
It might be a little easier if you wrap this in a macro:
#define NULL_TO_NIL(obj) ({ __typeof__ (obj) __obj = (obj); __obj == [NSNull null] ? nil : obj; })
Then you can write things like
fight.winnerID = NULL_TO_NIL([dict objectForKey:#"winner"]);
Alternatively you can pre-process your dictionary and replace all NSNulls with nil before even trying to stuff it into your managed object.
Ok, I've just woke up this morning with a good solution. What about this:
Serialize the JSON using the option to receive Mutable Arrays and Dictionaries:
NSMutableDictionary *rootDict = [NSJSONSerialization JSONObjectWithData:_receivedData options:NSJSONReadingMutableContainers error:&error];
...
Get a set of keys that have [NSNull null] values from the leafDict:
NSSet *nullSet = [leafDict keysOfEntriesWithOptions:NSEnumerationConcurrent passingTest:^BOOL(id key, id obj, BOOL *stop) {
return [obj isEqual:[NSNull null]] ? YES : NO;
}];
Remove the filtered properties from your Mutable leafDict:
[leafDict removeObjectsForKeys:[nullSet allObjects]];
Now when you call fight.winnerID = [dict objectForKey:#"winner"]; winnerID is automatically going to be (null) or nil as opposed to <null> or [NSNull null].
Not relative to this, but I also noticed that it is better to use a NSNumberFormatter when parsing strings to NSNumber, the way I was doing was getting integerValue from a nil string, this gives me an undesired NSNumber of 0, when I actually wanted it to be nil.
Before:
// when [leafDict valueForKey:#"round"] == nil
fight.round = [NSNumber numberWithInteger:[[leafDict valueForKey:#"round"] integerValue]]
// Result: fight.round = 0
After:
__autoreleasing NSNumberFormatter* numberFormatter = [[NSNumberFormatter alloc] init];
fight.round = [numberFormatter numberFromString:[leafDict valueForKey:#"round"]];
// Result: fight.round = nil
I wrote a couple of category methods to strip nulls from a JSON-generated dictionary or array prior to use:
#implementation NSMutableArray (StripNulls)
- (void)stripNullValues
{
for (int i = [self count] - 1; i >= 0; i--)
{
id value = [self objectAtIndex:i];
if (value == [NSNull null])
{
[self removeObjectAtIndex:i];
}
else if ([value isKindOfClass:[NSArray class]] ||
[value isKindOfClass:[NSDictionary class]])
{
if (![value respondsToSelector:#selector(setObject:forKey:)] &&
![value respondsToSelector:#selector(addObject:)])
{
value = [value mutableCopy];
[self replaceObjectAtIndex:i withObject:value];
}
[value stripNullValues];
}
}
}
#end
#implementation NSMutableDictionary (StripNulls)
- (void)stripNullValues
{
for (NSString *key in [self allKeys])
{
id value = [self objectForKey:key];
if (value == [NSNull null])
{
[self removeObjectForKey:key];
}
else if ([value isKindOfClass:[NSArray class]] ||
[value isKindOfClass:[NSDictionary class]])
{
if (![value respondsToSelector:#selector(setObject:forKey:)] &&
![value respondsToSelector:#selector(addObject:)])
{
value = [value mutableCopy];
[self setObject:value forKey:key];
}
[value stripNullValues];
}
}
}
#end
It would be nice if the standard JSON parsing libs had this behaviour by default - it's almost always preferable to omit null objects than to include them as NSNulls.
Another method is
-[NSObject setValuesForKeysWithDictionary:]
In this scenario you could do
[fight setValuesForKeysWithDictionary:dict];
In the header NSKeyValueCoding.h it defines that "Dictionary entries whose values are NSNull result in -setValue:nil forKey:key messages being sent to the receiver.
The only downside is you will have to transform any keys in the dictionary to keys that are in the receiver. i.e.
dict[#"winnerID"] = dict[#"winner"];
[dict removeObjectForKey:#"winner"];
I was stuck with the same problem, found this post, did it in a slightly different way.Using category only though -
Make a new category file for "NSDictionary" and add this one method -
#implementation NSDictionary (SuperExtras)
- (id)objectForKey_NoNSNULL:(id)aKey
{
id result = [self objectForKey:aKey];
if(result==[NSNull null])
{
return nil;
}
return result;
}
#end
Later on to use it in code, for properties that can have NSNULL in them just use it this way -
newUser.email = [loopdict objectForKey_NoNSNULL:#"email"];
Thats it
In the following method, I'm unsure of why releasing one of the arrays leads to an exception. The only reason that I could see, would be if componentsSeparatedByString returns an autoreleased array, but I can't see that the documentation mentions that it do.
-(void)addRow:(NSString *)stringWithNumbers;
{
NSArray *numbers = [stringWithNumbers componentsSeparatedByString:#" "];
NSMutableArray *row = [[NSMutableArray alloc] initWithCapacity:[numbers count]];
for (NSString *number in numbers) {
Number *n = [[Number alloc] initWithNumber:number];
[row addObject:n];
[n release];
}
[rows addObject:row];
[row release];
// [numbers release]; <-- leads to exception
}
Can anyone confirm if the array is autoreleased? If so, how can I know/why should I have known?
Is it possible to check if any one instance of an object is autoreleased or not by code?
Yes, because the name of the method:
does not start with new
does not start with alloc
is not retain
does not contain copy
This is commonly known as the "NARC" rule, and is fully explained here: http://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/MemoryMgmt/Articles/mmObjectOwnership.html#//apple_ref/doc/uid/20000043-SW1
unless you specifically allocate memory, a system method will give you back an autoreleased method.
By convention all methods with init or copy in their names return non-autoreleased objects.