PDA

View Full Version : [NSArray count] Problem (exception thrown)




Fuzzball27
Feb 15, 2012, 02:44 PM
I have a problem with this section of code:

NSArray *array = [[[NSArray alloc] init] autorelease];
array = [self getSavedChanges];

NSLog(@"%u", [array count]);

Here is the getSavedChanges method:

- (NSArray *)getSavedChanges
{
NSArray *array = [[[NSArray alloc] init] autorelease];
array = [NSKeyedUnarchiver unarchiveObjectWithFile:pathInDocumentDirectory(@"points.data")];
return array;
}

Here is the pathInDocumentDirectory() function:

NSString *pathInDocumentDirectory(NSString *filename)
{
NSArray *documentDirectories = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentDirectory = [documentDirectories objectAtIndex:0];
return [documentDirectory stringByAppendingPathComponent:filename];
}

The problem that I am experiencing is that after my objects are unarchived and added to the NSArray, they are sent the message "count." This happens when I send "count" to the NSArray. I don't want the count of the objects IN the NSArray, I want the count of the NSArray itself... Why are the objects within the NSArray being sent "count"?

Just in case you were wondering, here is the code from the debugger where the exception is thrown. "MapPoint" is the name of the class of the unarchived objects:

2012-02-15 15:32:01.342 MyLocation[16596:11c03] -[MapPoint count]: unrecognized selector sent to instance 0x6d8d540
2012-02-15 15:32:01.362 MyLocation[16596:11c03] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MapPoint count]: unrecognized selector sent to instance 0x6d8d540'
*** First throw call stack:
(0x157d052 0x139dd0a 0x157eced 0x14e3f00 0x14e3ce2 0x2c53 0x23864e 0x198a73 0x198ce2 0x198ea8 0x19fd9a 0x170be6 0x1718a6 0x180743 0x1811f8 0x174aa9 0x1380fa9 0x15511c5 0x14b6022 0x14b490a 0x14b3db4 0x14b3ccb 0x1712a7 0x172a9b 0x1d02 0x1c75)
terminate called throwing an exception(gdb)



robbieduncan
Feb 15, 2012, 03:55 PM
Why do you keep creating autoreleased array instances that you don't use and then instantly assign object to that variable?

jnoxx
Feb 15, 2012, 04:15 PM
You are having heavy memory leaks.. you can assign a pointer with a return method, instead of assigning a new one again..

KnightWRX
Feb 15, 2012, 05:07 PM
You're not checking anything. Look at NSKeyedUnarchiver and the unarchiveObjectWithFile: method, it returns an id type, not an NSArray type.

The method says it does this :

Decodes and returns the object graph previously encoded by NSKeyedArchiver written to the file at a given path.

Are you sure points.data contained an object of type NSArray ? Have you tried checking whatever got return's class property to make sure ?

Sydde
Feb 15, 2012, 07:38 PM
Did you in fact archive a NSArray of MapPoint objects? Please show the code that does that.

Fuzzball27
Feb 15, 2012, 09:17 PM
Why do you keep creating autoreleased array instances that you don't use and then instantly assign object to that variable?

You are having heavy memory leaks.. you can assign a pointer with a return method, instead of assigning a new one again..

I'm not quite sure what you guys are saying (I'm obviously new with OOP), but does this fix it?
NSArray *array = [self getSavedChanges];

//and

NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:pathInDocumentDirectory(@"points.data")];


You're not checking anything. Look at NSKeyedUnarchiver and the unarchiveObjectWithFile: method, it returns an id type, not an NSArray type.

The method says it does this :

Quote:
Decodes and returns the object graph previously encoded by NSKeyedArchiver written to the file at a given path.
Are you sure points.data contained an object of type NSArray ? Have you tried checking whatever got return's class property to make sure ?

Did you in fact archive a NSArray of MapPoint objects? Please show the code that does that.


I archived the MapPoints one by one from the annotations array in an MKMapView instance. If I use archiveRootObject (MKMapView.annotations) it crashes, and if I archive the last object of the same array it crashes. So I settled with this:

- (void)save
{
for (int i = 0; i + 1 < worldView.annotations.count; i++) {
[NSKeyedArchiver archiveRootObject:[worldView.annotations objectAtIndex:i] toFile:pathInDocumentDirectory(@"points.data")];
NSLog(@"Archived: %@", [[worldView.annotations objectAtIndex:i] title]);
}
}

KnightWRX
Feb 16, 2012, 04:12 AM
You do know you can check an object's class at runtime right ?


id array;

array = returnFromFunctionOrMethod();

if([array isKindOfClass: [NSArray class]])
NSLog(@"Well gosh darn it is an array!");
else
NSLog(@"We got something else...");

robbieduncan
Feb 16, 2012, 04:21 AM
NSArray *array = [[[NSArray alloc] init] autorelease];
array = [self getSavedChanges];
}
I'm not quite sure what you guys are saying (I'm obviously new with OOP), but does this fix it?
NSArray *array = [self getSavedChanges];

//and

NSArray *array = [NSKeyedUnarchiver unarchiveObjectWithFile:pathInDocumentDirectory(@"points.data")];



In the first code you are declaring a new pointer variable (array) and creating a new object that you assign to that variable. You then instantly assign a totally different object to it. Clearly the creation of the autoreleased array is pointless.

As for fixing it it solves the problem I pointed out. It does not solve the problem that array may well be nil if points.data does not exist or does not contain a valid datastructure for the keyed unarchiver.

It appears you have little formal training. I would suggest your read up on and consider defensive programming. Consider what could happen in each possible outcome of a method call. For example in the above you may have a valid NSArray instance. Or you may have nil. You completely ignore the second case which is quite possibly giving you the error you see.

amorya
Feb 16, 2012, 04:30 AM
Consider what could happen in each possible outcome of a method call. For example in the above you may have a valid NSArray instance. Or you may have nil.

Or you may have an instance of something else.

Just because a variable is of type NSArray, doesn't mean that you have an NSArray in it.

For debugging purposes, try adding this line:

NSLog(@"Class of contents of 'array' variable: %@", NSStringFromClass([array class]));

Add that line just after the array = [self getSavedChanges]; line. Then find the line it prints out in the debug console. That'll tell you what class you've actually got.

Amorya

KnightWRX
Feb 16, 2012, 05:14 AM
For debugging purposes, try adding this line:

NSLog(@"Class of contents of 'array' variable: %@", NSStringFromClass([array class]));


Correct me if I'm wrong, but according to the NSObject reference, this line doesn't work. class is a Class method, not an instance method. You can't call it on an instance. To do what you want to do, you'd use the instance method className :

https://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/nsobject_Class/Reference/Reference.html

class

Returns the class object.
+ (Class)class
Return Value

The class object.

className

Returns a string containing the name of the class.
- (NSString *)className
Return Value

A string containing the name of the class.

So really, your code would look like this instead :

NSLog(@"Class of contents of 'array' variable: %@", [array className]);

But really, the OP wants the snippet I posted. Testing what you got is always good practice, especially when loading objects dynamically like that. If you get what you want, go ahead and do your stuff, if not, fail gracefully or try to recover. isKindOfClass permits that kind of coding practice which robbie was talking about, defensive programming.

Fuzzball27
Feb 16, 2012, 07:29 AM
Thank you all for the help.
[array class] returned an instance of MapPoint meaning that I screwed up in the archiving process. I realized this as I was creating my last post: rather than archiving the root object then retrieving the root object, I was archiving each individual map point to points.data then using UnarchiveObjectWithFile as if I had archived the root object to points.data. UnarchiveObjectWithFile was only getting one object from points.data rather than magically creating an array from all objects in points.data.

So I've edited the saveChanges method to this:
- (void)save
{
NSLog(@"%@", worldView.annotations);
NSMutableArray *array = [NSMutableArray arrayWithArray:worldView.annotations];
[array removeObjectAtIndex:array.count - 1];
NSLog(@"%@", array);

[NSKeyedArchiver archiveRootObject:array toFile:pathInDocumentDirectory(@"points.data")];
}