views:

920

answers:

4

Hi,

I need to save my own created class to file, I found on the internet, that good approach is to use NSKeyedArchiver and NSKeyedUnarchiver My class definition looks like this:

@interface Game : NSObject <NSCoding> {

    NSMutableString *strCompleteWord;
    NSMutableString *strWordToGuess;

    NSMutableArray *arGuessedLetters;    //This array stores characters
    NSMutableArray *arGuessedLettersPos;  //This array stores CGRects

    NSInteger iScore;
    NSInteger iLives;
    NSInteger iRocksFallen;

    BOOL bGameCompleted;
    BOOL bGameOver;
}

I've implemented methods initWithCoder: and encodeWithCoder: this way:

- (id)initWithCoder:(NSCoder *)coder
    {   
        if([coder allowsKeyedCoding])
        {
         strCompleteWord = [[coder decodeObjectForKey:@"CompletedWord"] copy];
         strWordToGuess = [[coder decodeObjectForKey:@"WordToGuess"] copy];
         arGuessedLetters = [[coder decodeObjectForKey:@"GuessedLetters"] retain];
        // arGuessedLettersPos = [[coder decodeObjectForKey:@"GuessedLettersPos"] retain];
         iScore = [coder decodeIntegerForKey:@"Score"];
         iLives = [coder decodeIntegerForKey:@"Lives"];
         iRocksFallen = [coder decodeIntegerForKey:@"RocksFallen"];
         bGameCompleted = [coder decodeBoolForKey:@"GameCompleted"];
         bGameOver = [coder decodeBoolForKey:@"GameOver"];
        }
        else
        {
         strCompleteWord = [[coder decodeObject] retain];
         strWordToGuess = [[coder decodeObject] retain];
         arGuessedLetters = [[coder decodeObject] retain];
        // arGuessedLettersPos = [[coder decodeObject] retain];
         [coder decodeValueOfObjCType:@encode(NSInteger) at:&iScore];
         [coder decodeValueOfObjCType:@encode(NSInteger) at:&iLives];
         [coder decodeValueOfObjCType:@encode(NSInteger) at:&iRocksFallen];
         [coder decodeValueOfObjCType:@encode(BOOL) at:&bGameCompleted];
         [coder decodeValueOfObjCType:@encode(BOOL) at:&bGameOver];
        }

        return self;
    }

    - (void)encodeWithCoder:(NSCoder *)coder
    {
        if([coder allowsKeyedCoding])
        {
         [coder encodeObject:strCompleteWord forKey:@"CompleteWord"];
         [coder encodeObject:strWordToGuess forKey:@"WordToGuess"];
         [coder encodeObject:arGuessedLetters forKey:@"GuessedLetters"];
         //[coder encodeObject:arGuessedLettersPos forKey:@"GuessedLettersPos"];
         [coder encodeInteger:iScore forKey:@"Score"];
         [coder encodeInteger:iLives forKey:@"Lives"];
         [coder encodeInteger:iRocksFallen forKey:@"RocksFallen"];
         [coder encodeBool:bGameCompleted forKey:@"GameCompleted"];
         [coder encodeBool:bGameOver forKey:@"GameOver"];
        }
        else
        {
         [coder encodeObject:strCompleteWord];
         [coder encodeObject:strWordToGuess];
         [coder encodeObject:arGuessedLetters];
         //[coder encodeObject:arGuessedLettersPos];
         [coder encodeValueOfObjCType:@encode(NSInteger) at:&iScore];
         [coder encodeValueOfObjCType:@encode(NSInteger) at:&iLives];
         [coder encodeValueOfObjCType:@encode(NSInteger) at:&iRocksFallen];
         [coder encodeValueOfObjCType:@encode(BOOL) at:&bGameCompleted];
         [coder encodeValueOfObjCType:@encode(BOOL) at:&bGameOver];
        }
    }

And I use these methods to archive and unarchive data:

[NSKeyedArchiver archiveRootObject:currentGame toFile:strPath];
Game *currentGame = [NSKeyedUnarchiver unarchiveObjectWithFile:strPath];

I have two problems.

1) As you can see, lines with arGuessedLettersPos is commented, it's because every time I try to encode this array, error comes up(this archiver cannot encode structs), and this array is used for storing CGRect structs. I've seen solution on the internet. The thing is, that every CGRect in the array is converted to an NSString (using NSStringFromCGRect()) and then saved. Is it a good approach?

2)This is bigger problem for me. Even if I comment this line and then run the code successfully, then save(archive) the data and then try to load (unarchive) them, no data is loaded. There aren't any error but currentGame object does not have data that should be loaded.

Could you please give me some advice? This is first time I'm using archivers and unarchivers.

Thanks a lot for every reply.

+1  A: 

I might be missing it, but I don't see any obvious bugs in this code.

Here are some ideas that might help:

  • Add some NSLog statements, and watch the debug output (open with command-shift-R in xcode) to see if your encode/decode methods are actually being called.
  • Check that the archive file is saved: Running in the simulator, you can save to any local path you want, such as /tmp/my_archive_file. Try to save to that file, and see if (a) the file exists with the right timestamp, and (b) you print out the file, you can see some recognizable strings (like "RocksFallen") in amongst the binary gooblygoo.

I also don't think it's necessary to check for allowsKeyed(En)coding since you know that's always going to be true when you're explicitly using NSKeyed(Un)archiver to do your dirty work for you. So you can throw away the other half of your code.

About coding those arrays of CGRects: I don't think you can directly add structs to an NSMutableArray, right? So they must be some kind of object, which means you can add NSCoding support to that object. If it's not your own object, you can subclass it and add that protocol, or try adding it as a category.

Tyler
A: 

Thanks for reply Tyler.

I looked in the saved file and there are recognizable strings like RocksFallen and so on, also there are some recognizable values that might be saved in string variables.. So this seems to be good.

I tried to put some NSLogs in the code and not everything seems to be good. So... When I launch simulator for the first time, there's no archive file, yes it is obvious, because nothing is saved. Then I change something and exit the application. When the application is being terminated, data might be archived, everything's fine. NSLog statements are written in the console, file is on the disk. But what is strange is that when I launch the application then, decode method(initwithcoder) is not being called.. Don't know why. Then I exit the simulator and run the simulator again. When I run it again, decode method is being called at the launchtime. But only at the first launchtime after running simulator, when I then work with simulator like exit the app and run it again, initWithCoder method is not being called and that's very strange for me..

So there are problems unarchiving it i think.

Jenicek
Sounds like you have some good clues here to work with.You could check within the code that the desired file exists. It might be being saved in a temporary location, or there might be a typo in the file name, etc. Another idea is to simply add an NSLog right before your call to unarchiveObjectWithFile: to check that that exact line is being executed. If you're feeling adventurous, you could add a breakpoint there and walk through the code.
Tyler
A: 

The problem with loading and saving solved another way...

Instead of implementing - (id)initWithCoder:(NSCoder )coder and - (void)encodeWithCoder:(NSCoder)coder I used this solution:

    NSMutableData *data = [NSMutableData alloc];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];

    [archiver encodeObject:self.strCompleteWord forKey:@"CompleteWord"];
    [archiver encodeObject:self.strWordToGuess forKey:@"WordToGuess"];
    [archiver encodeObject:self.arGuessedLetters forKey:@"GuessedLetters"];
    //[coder encodeObject:self.arGuessedLettersPos forKey:@"GuessedLettersPos"];
    [archiver encodeInteger:self.iScore forKey:@"Score"];
    [archiver encodeInteger:self.iLives forKey:@"Lives"];
    [archiver encodeInteger:self.iRocksFallen forKey:@"RocksFallen"];
    [archiver encodeBool:self.bGameCompleted forKey:@"GameCompleted"];
    [archiver encodeBool:self.bGameOver forKey:@"GameOver"];

    [archiver finishEncoding];

    [data writeToFile:strPath atomically:YES];

    [data release];

and

    NSMutableData *data = [[NSMutableData alloc] initWithContentsOfFile:strPath];
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

    self.strCompleteWord = [[unarchiver decodeObjectForKey:@"CompletedWord"] copy];
    self.strWordToGuess = [[unarchiver decodeObjectForKey:@"WordToGuess"] copy];
    self.arGuessedLetters = [[unarchiver decodeObjectForKey:@"GuessedLetters"] retain];
    //self.arGuessedLettersPos = [[unarchiver decodeObjectForKey:@"GuessedLettersPos"] retain];     
    self.iScore = [unarchiver decodeIntegerForKey:@"Score"];
    self.iLives = [unarchiver decodeIntegerForKey:@"Lives"];
 self.iRocksFallen = [unarchiver decodeIntegerForKey:@"RocksFallen"];
   self.bGameCompleted = [unarchiver decodeBoolForKey:@"GameCompleted"];
    self.bGameOver = [unarchiver decodeBoolForKey:@"GameOver"];

    [unarchiver finishDecoding];
    [data release];

And this works totally fine :)

Jenicek
A: 

The best way to archive structs (that are not holding any pointers) with would be to wrap the values in an NSData block.

here, i'm dumping a struct array to the archiver:

for (int i = 0; i < items; i++) {

   NSString *keyValue = [NSString stringWithFormat:@"YourStruct%i", i ];
   NSData    *dataset = [NSData dataWithBytes:(void*)&activeprofile[i] length:(sizeof(profileset))];  

   [encoder encodeObject:dataset forKey:keyValue];    // makes a nice, solid data block

}

and reading the data back in:

for (int i = 0; i < items; i++) {

    NSString *keyValue = [NSString stringWithFormat:@"YourStruct%i", i ];
    NSData    *dataset = [decoder decodeObjectForKey:keyValue];

    [dataset getBytes:&activeprofile[i] length:sizeof(profileset)];

}

There you have it: easy as pie and extremely flexible with datatypes!

A similar method for non-keyed archiving can be found here: http://borkware.com/quickies/one?topic=NSCoder Though I suggest sticking with keyed archiving, as NSArchive is not supported on the iPhone and has been classified as legacy code.

bitcruncher