views:

982

answers:

4

Hi. I was curious what is considered the better way to manage the reading and writing of a High Score plist file. My High Score class is:

@interface HighScore : NSObject <NSCoding> {
    NSString  *name;
    int    score;
    int    level;
    int    round;
    NSDate   *date;
}

Now, I could do method A, add NSCoding methods:

- (void) encodeWithCoder: (NSCoder *) coder {
    [coder encodeObject: name
        forKey: kHighScoreNameKey];
    [coder encodeInt: score
        forKey: kHighScoreScoreKey];
    [coder encodeInt: level
        forKey: kHighScoreLevelKey];
    [coder encodeInt: round
        forKey: kHighScoreRoundKey];
    [coder encodeObject: date
        forKey: kHighScoreDateKey];
} // encodeWithCoder

- (id) initWithCoder: (NSCoder *) decoder {
    if (self = [super init]) {
     self.name = [decoder decodeObjectForKey: kHighScoreNameKey];
     self.score = [decoder decodeIntForKey: kHighScoreScoreKey];
     self.level = [decoder decodeIntForKey: kHighScoreLevelKey];
     self.round = [decoder decodeIntForKey: kHighScoreRoundKey];
     self.date = [decoder decodeObjectForKey: kHighScoreDateKey];
    }
    return (self);
} // initWithCoder

And write it all out with:

if (![NSKeyedArchiver archiveRootObject:highScoresList toFile:path]) ...

Reading it back in would be pretty straight forward. However the plist file, IMHO, looks like crap.

Or I could employ method B:

NSMutableArray *array = [NSMutableArray arrayWithCapacity:20];;
for (HighScore *hs in highScoresList) {
    NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
           hs.name, kHighScoreNameKey,
           [NSNumber numberWithInteger:hs.score], kHighScoreScoreKey,
           [NSNumber numberWithInteger:hs.level], kHighScoreLevelKey,
           [NSNumber numberWithInteger:hs.round], kHighScoreRoundKey,
           hs.date, kHighScoreDateKey,
           nil];
    [array addObject:dict];
    [dict release];
}

and write it all out with:

if (![array writeToFile:path atomically:YES]) ...

Reading it back in is a tiny bit harder. But the plist file looks much cleaner (smaller and compact).

Any thoughts? Am I missing something that is much simpler? (I want to keep the High Scores separate from NSUserDefaults so I am not using that).

A: 

check out this question, maybe it's useful for your app

Konstantinos
+1  A: 

Both your ways look fine to me. There is also Core Data in the 3.0 OS, although it might be overkill if all you want to save is a single high score value.

U62
+1  A: 

I'm not sure that I understand your objections! Both should work just fine.

Personally I prefer method A. I think it makes sense that an object knows how to encode itself. It makes maintenance easier and any changes more localised. Plus it probably uses less memory.

Stephen Darlington
A: 

I went with method B because: 1. The plist file is more readable and 2. I can save off some file and class version numbering into this method easily:

In my HighScore class:

- (id)initFromDictionary: (NSDictionary *)dict;
{
    if (self = [super init]) {
     self.name = [dict objectForKey:kHighScoreNameKey];
     self.score = [[dict objectForKey:kHighScoreScoreKey] integerValue];
     self.game = [[dict objectForKey:kHighScoreGameKey] integerValue];
     self.level = [[dict objectForKey:kHighScoreLevelKey] integerValue];
     self.date = [dict objectForKey:kHighScoreDateKey];
    }
    return (self);
}
- (NSDictionary *)putIntoDictionary;
{
    NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
           name, kHighScoreNameKey,
           [NSNumber numberWithInt:score], kHighScoreScoreKey,
           [NSNumber numberWithInt:game], kHighScoreGameKey,
           [NSNumber numberWithInt:level], kHighScoreLevelKey,
           date, kHighScoreDateKey,
           nil];
    return dict;
}

And in my HighScoreTable class:
- (id) load
{
    NSString *path = [self getFilePath];

//  [self clear];
    NSDictionary *rootLevelPlistDict = [NSDictionary dictionaryWithContentsOfFile:path];
    int versNum = [[rootLevelPlistDict objectForKey:kHighScoreVersKey] integerValue];

    if (versNum == kHighScoreVersNum) {
     NSArray *insideArray = [rootLevelPlistDict objectForKey:kHighScoresKey];

     NSDictionary *dict;
     for (dict in insideArray) {
      HighScore *hs = [[HighScore alloc] initFromDictionary:dict];
      [highScoresList addObject:hs];
      [hs release];
     }
    }
    return sharedHighScoresSingleton;
}


- (void) save
{   
    if (!changed)
     return;
    NSString *path = [self getFilePath];
    NSMutableArray *array = [NSMutableArray arrayWithCapacity:kNumberOfHighScores];
    for (HighScore *hs in highScoresList) {
     NSDictionary *dict = [hs putIntoDictionary];
     [array addObject:dict];
     [dict release];
    }
    NSDictionary *rootLevelPlistDict = [[NSDictionary alloc] initWithObjectsAndKeys: 
      [[[NSBundle mainBundle] infoDictionary]
       objectForKey:(NSString*)kCFBundleNameKey], kHighScoreAppNameKey,
       [NSNumber numberWithInt:kHighScoreHeaderVersNum], kHighScoreHeaderVersKey,
       [NSNumber numberWithInt:kHighScoreVersNum], kHighScoreVersKey,
       [NSDate date], kHighScoreCreationKey,
       array, kHighScoresKey,
       nil];

    if (![rootLevelPlistDict writeToFile:path atomically:YES])
     NSLog(@"not successful in writing the high scores");
    [rootLevelPlistDict release];
}
mahboudz
Actually, NSCoding methods can also do versioning using the "+ (NSInteger)version" class method. This is how I used to do upgrades in my app.
Stephen Darlington