NSKeyedUnarchiver does not call initWithCoder - objective-c

I've used the following example:
game state singleton cocos2d, initWithEncoder always returns null
Instead of using a file, I use an NSData object i am getting from NSUserDefaults. It is not null, but rather have data in it that I believe is the correctly encoded data and for some reason when I run unarchiveObjectWithData it does not call initWithCoder as I put an NSLog on the first line.
Ideas?
+ (void)loadState {
#synchronized([AppStateManager class]) {
if (!sharedAppStateManager) {
[AppStateManager sharedManager];
}
NSUserDefaults *udefs = [NSUserDefaults standardUserDefaults];
NSData *archivedData = [udefs objectForKey:#"AppState"];
NSLog(#"going to unarchive");
[NSKeyedUnarchiver unarchiveObjectWithData:archivedData];
NSLog(#"unarchive complete");
}
}
- (id)initWithCoder:(NSCoder *)decoder {
NSLog(#"init coder");
self = [super init];
if (self != nil) {
[self setUsername:[decoder decodeObjectWithKey:#"username"]];
}
return self;
}

Solved.
The example posted at the link above initializes the object via "sharedInstance" using the allocWithZone method. If the object exists it returns nil.
the NSKeyedUnarchiver tries to alloc AGAIN via allocWithZone and since the object has already been initialized earlier in that very same method, or sometimes before hand the unarchiver gets a nil instead of an allocated object.
Hope this helps someone.

I don't know why, but after I do this to obtain my NSData of the archive (the contents of which i think is inconsequential):
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
NSData *encodedConfig = [userDefaults valueForKey:kConfiguration];
When I execute this, it returns nil.
NSArray *configs = [NSKeyedUnarchiver unarchiveObjectWithData:encodedConfig];
But when I execute this, I get the expected, previously archived, array of objects.
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:encodedConfig];
NSArray *configs = [unarchiver decodeObject];
I expect identical behavior from these two approaches. My only response is avoid unarchiveObjectWithData:.

This isn't so much an answer but a bit of additional info I found while trying to work a similar issue out. I guess I am still under rookie status, so this is the only way I could find to contribute to this post.
Anyways...
Thanks so much for banging through this issue...especially over here:
http://www.cocos2d-iphone.org/forum/topic/11327
I was having a problem with a mutable array working using the method you had worked out where it would switch to an immutable array after I unarchived it from the file.
Casting the returned array as a mutable version did not work either.
I end up adding objectsFromArray to my mutable array and this solved my problems.
Worked:
[self.mutableArray addObjectsFromArray:[decoder decodeObjectForKey:#"MutableArray"]];
Neither of these worked:
self.mutableArray = [decoder decodeObjectForKey:#"MutableArray"];
self.mutableArray = (NSMutableArray *)[decoder decodeObjectForKey:#"MutableArray"];

Related

Passing a reference and copying the object from another controller. Objects keep disappearing

I know this is a relativly easy question, but I just can't figure out how to solve this problem:
One of my views will receive a dragOperation and the performDragOperation method should pass the NSURL to my AppDelegate which puts it in an mutArray...
The problem is, that I pass over a reference and the object disappears the moment performDragOperation is done. So I tried several things:
in performDragOperation:
//Puts the reference itself in the folderPaths Array
AppDelegate *appDelegate = [NSApp delegate];
[[appDelegate folderPaths] addObject:referenceTotheNSURLObject];
/* tried creating a NSString and putting it in too. Same result because local variables will disappear */
So I created a method in AppDelegate and tried different things:
- (void)addFilePath:(NSURL *)filePath {
NSURL *copyOfURL = [filePath copy];
[[self folderPaths] addObject:copyOfURL];
}
This makes the most sense to me and the only reason I can think about why this doesn't work is, because copyOfURL itself is a pointer and it points to an localObject that will disappear the moment the addFilePath method is finished? But I'm not allowed to use
NSURL copyOfURL = [filePath copy];
I also tried recreating an NSString object again. Same results.
- (void)addFilePath:(NSURL *)filePath {
NSString *pathString = [filePath absoluteString];
[[self folderPaths] addObject:pathString];
}
I know it will be some relatively simply solution but I'm stuck and can't figure it out. Thanks for your time!

NSKeyedArchiver and NSKeyedUnarchiver with NSMutableArray

I'm hoping this isn't something to do with the fact that I'm using a Mutable array here, but this one is baffling me so it wouldn't surprise me if that were the case.
BACKGROUND:
I have made a small database which is essentially an NSMutableArray containing custom objects, which we can call recordObjects. I set up the array:
database = [[NSMutableArray alloc] init];
and my custom object, called "recordObject" contains the following variables and inits:
NSString *name;
int anInt;
Bool aBool;
I also synthesized methods so I can make calls like:
aString = [[database objectAtIndex:someIndex] name];
And added methods to my controller class to add, remove, and select the individual records for display. So far everything works correctly and exactly as expected.
Next, I've set up my recordObject class (subclass of NSObject) to use the NSCoder (by including in the #interface directive, and have added the following custom encoder and decoder methods in the implementation file:
-(void) encodeWithCoder: (NSCoder *) encoder {
[encoder encodeObject: name forKey: #"recordName"];
[encoder encodeInt: anInt forKey: #"recordInteger"];
[encoder encodeBool: aBool forKey: #"recordBool"];
}
-(id) initWithCoder: (NSCoder *) decoder {
name = [decoder decodeObjectForKey: #"recordName"];
anInt = [decoder decodeIntForKey: #"recordInteger"];
aBool = [decoder decodeBoolForKey: #"recordBool"];
}
In order to write the file, I have used the following:
[NSKeyedArchiver archiveRootObject:database toFile:myPath];
When I run the program, everything APPEARS to work correctly. The encoder method is called for each of the records in the array and the file is written to disk. Opening the file with TextEdit shows that the data is there (though mostly unintelligible to me.)
THE PROBLEM:
Here's where I run into a snag.
I added the following code to LOAD the file into my database array:
database = [NSKeyedUnarchiver unarchiveObjectWithFile:myPath];
When I run the program again, this time Loading the database, it APPEARS to work correctly. My first test was to use the NSMutableArray count method:
x = [database count];
The result was that X is filled with the correct number of records in the file. If there were 5 records when I saved the database, X is set to 5 after loading the database on the next execution of the program.
Now, here's the big problem:
The program crashes if I try to use ANY of my accessor methods. For example, if I try to use the following after loading the database:
aString = [[database objectAtIndex:someIndex] name];
the program crashes and returns the following error in the console:
Program received signal: “EXC_BAD_ACCESS”.
sharedlibrary apply-load-rules all
My interpretation is that the data is not being loaded and initialized into the database array correctly for some reason, but for the life of me I can't figure out where I've gone wrong here.
As a side note, everything I've implemented came from Stephen G. Kochan's book "Programming in Objective-C"
Any ideas would be greatly appreciated!
There are a few problems with your code.
Your initWithCoder: method is not fully implemented. You must call [super init] and return self. You must also copy or retain the string object, otherwise it will be autoreleased:
- (id)initWithCoder:(NSCoder *)decoder
{
self = [super init];
if(self)
{
name = [[decoder decodeObjectForKey: #"recordName"] copy];
anInt = [decoder decodeIntForKey: #"recordInteger"];
aBool = [decoder decodeBoolForKey: #"recordBool"];
}
return self;
}
The other problem is with this line:
database = [NSKeyedUnarchiver unarchiveObjectWithFile:myPath];
That is fine, except for two things:
You're not holding a reference to the object, so it will be
autoreleased.
NSKeyedArchiver returns an immutable object, in this case an NSArray and not an NSMutableArray.
You need to do this:
database = [[NSKeyedUnarchiver unarchiveObjectWithFile:myPath] mutableCopy];
That will both retain the object (because it's a copy) and make the object an NSMutableArray.
It doesn't appear that you're initializing the recordObjects in -initWithCoder:
Try something like this:
-(id) initWithCoder: (NSCoder *) decoder {
self = [super init];
if (self){
name = [decoder decodeObjectForKey: #"recordName"] copy];
anInt = [decoder decodeIntForKey: #"recordInteger"];
aBool = [decoder decodeBoolForKey: #"recordBool"];
}
return self;
}
The data is there when you archive it but you're not properly unarchiving it.
Sounds like a memory management issue to me. EXC_BAD_ACCESS usually means that you're trying to access an object that has been deallocated. unarchiveObjectWithFile: returns an autoreleased object, so you have to retain it if you want to keep it around, either with an explicit retain or by assigning it to a retained property.
I was having the same issue when unarchiving a custom object
self.calTable = [[NSKeyedUnarchiver unarchiveObjectWithFile:calibrationFile] objectForKey:#"calTable"];
Based on Rob's answer I changed to
self.calTable = [[[NSKeyedUnarchiver unarchiveObjectWithFile:calibrationFile] mutableCopy] objectForKey:#"calTable"];
and it fixed all errors.

Strange "mutating method sent to immutable object" error when adding an object to a mutable array

This is a strange one...
Periodically I am checking if the user has achieved a new top score in my app. Here is my code:
- (void) newTopScore
{
// pull old top score from the database
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSDictionary *getBoardsAndScores = [defaults dictionaryRepresentation];
NSMutableArray *boardsAndScores = [[getBoardsAndScores objectForKey:#"boardsAndScores"] mutableCopy];
NSMutableDictionary *boardAndScoreObject = [[boardsAndScores objectAtIndex:gameBoardIndex] mutableCopy];
NSString *topscore = [boardAndScoreObject objectForKey:#"topscore"];
NSLog(#"topscore in value: %i", [topscore intValue]);
if ([topscore intValue] == 0)
{
NSString *topscoreString = [[NSString alloc] initWithFormat:#"%i", [self countCurrentPegs]];
NSMutableArray *mutableBoardsAndScores = [boardsAndScores mutableCopy];
[[mutableBoardsAndScores objectAtIndex:gameBoardIndex] setObject:topscoreString forKey:#"topscore"];
[defaults setObject:mutableBoardsAndScores forKey:#"boardsAndScores"];
[defaults synchronize];
}
else if ([self countCurrentPegs] < [topscore intValue])
{
NSString *topscoreString = [[NSString alloc] initWithFormat:#"%i", [self countCurrentPegs]];
NSMutableArray *mutableBoardsAndScores = [boardsAndScores mutableCopy];
[[mutableBoardsAndScores objectAtIndex:gameBoardIndex] setObject:topscoreString forKey:#"topscore"];
[defaults setObject:mutableBoardsAndScores forKey:#"boardsAndScores"];
[defaults synchronize];
}
}
Sometimes the code works just fine, both for the if and the else. Sometimes though I get the following error:
*** Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFDictionary setObject:forKey:]:
mutating method sent to immutable object'
I've tried setting break points and stepping through the program line by line. The crash seems somewhat random and I can't work out a pattern. It usually crashes on the second...
[[mutableBoardsAndScores objectAtIndex:gameBoardIndex] setObject:topscoreString forKey:#"topscore"];
...line.
The object IS mutable. Or at least it was. Why is it changing?
Any help of advice would be very appreciated.
Thanks.
[[[mutableBoardsAndScores objectAtIndex:gameBoardIndex] mutableCopy] setObject:topscoreString forKey:#"topscore"];
Could you test this? (added the mutableCopy for the gameBoard);
EDIT
could you change the above to:
NSMutableArray *gameBoard = [[mutableBoardsAndScores objectAtIndex:gameBoardIndex] mutableCopy];
[gameBoard setObject:topscoreString forKey:#"topscore"];
[mutableBoardsAndScores removeObjectAtIndex:gameBoardIndex];
[mutableBoardsAndScores insertObject:gameBoard atIndex:gameBoardIndex];
Could you try this? It might not be the most beautiful solution but i believe it will work.
NSUserDefaults does not store the objects you set to it. It stores the data. Effectively, it's making a copy. And any collection object you retrieve from NSUserDefaults will be immutable.
Edited to say: also, when you make a mutable copy of a collection, it's a shallow copy. You get a mutable collection holding the same objects as the original collection. If the original collection held immutable objects, then so will the new collection.
If you need to do a deep copy of a property-list compatible hierarchy of objects, getting mutable objects back, you can use NSPropertyListSerialization. Combine +dataWithPropertyList:format:options:error: with +propertyListWithData:options:format:error: and the NSPropertyListMutableContainers or NSPropertyListMutableContainersAndLeaves options.

Cannot add items to an NSMutableArray ivar

My goal is to add a string to array, and I do that in a method which I call.
In this method, I get a null value in the array, and don't know why. I have this at the start of my class:
NSMutableArray *listOfEvents;
and a method which I call on each event:
-(void)EventList
{
[listOfEvents addObject:#"ran"];
NSLog(#"%#", listOfEvents);
}
I get (null) in the log.
If I put the array definition NSMutableArray *listOfEvents; in the function body, I get the string value #"ran", each time, so the array always has only one value, instead of having many strings named #"ran".
What's wrong with this? It seems that I can't understand something about arrays, even though I have read the documents a number of times.
I'm assuming you haven't initialized listOfEvents.
Make sure you do listOfEvents = [[NSMutableArray alloc] init]; in your class's init method. Also make sure you release it in your class's dealloc method.
If you're getting nil in your log message, you need to make sure listOfEvents is non-nil before adding your object. IE:
-(void)EventList
{
if (listOfEvents == nil) {
listOfEvents = [[NSMutableArray alloc] init];
}
[listOfEvents addObject:#"ran"];
NSLog(#"%#",listOfEvents);
}
In Objective-C, messages with void return types sent to nil go to absolutely-silent nowhere-land.
Also, for the sake of balance, be sure you have a [listOfEvents release] call in your dealloc implementation.
Apparently you're not initializing your array.
NSMutableArray *listOfEvents = [[NSMutableArray alloc] init];
If that's your problem, I suggest reading the docs again. And not the NSMutableArray docs. Go back to The Objective-C Programming Language and others.
You need to alloc the NSMutableArray. Try doing this first -
NSMutableArray *listOfEvents = [[NSMutableArray alloc] init];
After this you could do what you what you planned...

NSUserDefaults on iPhone SDK not returning nil

I'm using NSUserDefaults for a Settings Application. This is a pristine copy and I have not at all added anything to the filesystem for this particular setting. I expect it to be nil, but on the contrary..
If I do something like the following:
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
if ( [defaults valueForKey:kIdentifierKey] == nil ) {
return YES;
}
else {
NSLog(#"we have a value. return NO. Value is: %#", [defaults objectForKey:kIdentifierKey]);
return NO;
}
Yet in NSLog, nothing shows where you'd expect the value. The else block does indeed get executed. Is there any reason why this is NOT nil?
valueForKey: is kind of a complex method in all the places it searches. I find it more reliable to use the getters for specific datatypes when dealing with defaults.
if ([defaults boolForKey:kIdentifierKey]) [self doSomething];
Or you could use objectForKey: which should return nil as you expect. In fact, you do this when you create your NSLog statement. And the string description of nil is #"" so your output is as you would expect.
Also, you are testing and logging different keys here:
[defaults valueForKey:kIdentifierKey];
[defaults objectForKey:kGroupIdentifierKey];
Making sure you are testing and logging what you think you are.
Test from a new iPhone project:
if (![[NSUserDefaults standardUserDefaults] valueForKey:#"foobar"]) NSLog(#"is not yet defined");
if (![[NSUserDefaults standardUserDefaults] objectForKey:#"foobar"]) NSLog(#"is not yet defined");
if ([[NSUserDefaults standardUserDefaults] valueForKey:#"foobar"] == nil) NSLog(#"valueForKey is nil");
if ([[NSUserDefaults standardUserDefaults] objectForKey:#"foobar"] == nil) NSLog(#"objectForKey is nil");
It outputs:
is not yet defined
is not yet defined
valueForKey is nil
objectForKey is nil
I think you have something else going on here.
Based on the docs....
"A default’s value must be a property
list, that is, an instance of (or for
collections a combination of instances
of): NSData, NSString, NSNumber,
NSDate, NSArray, or NSDictionary. If
you want to store any other type of
object, you should typically archive
it to create an instance of NSData.
For more details, see User Defaults
Programming Topics for Cocoa."
Also, [defaults valueForKey:] is not an instance method according to the documentation, so the result is most likely undefined. If you use [defaults objectForKey:] it will return nil if the object associated with kIdentifierKey is not found.