4

I'm trying to store some custom class/data to a file in my iPhone/iPad app.

I have a Class RSHighscoreList

@interface RSHighscoreList : NSObject {
    NSMutableArray *list;
}

which contains objects of RSHighscore in the list

@interface RSHighscore : NSObject {
    NSString *playerName;
    NSInteger points;
}

When I try to store all to file

- (void)writeDataStore {
    RSDataStore *tmpStore = [[RSDataStore alloc] init];
    _tmpStore.highscorelist = self.highscorelist.list;
    NSMutableData *data = [[NSMutableData alloc] init];
    NSKeyedArchiver *archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];

    [archiver encodeObject:tmpStore forKey:kDataKey];
    [archiver finishEncoding];
    [data writeToFile:[self dataFilePath] atomically:YES];

    [archiver release];
    [data release];
}

@interface RSDataStore : NSObject <NSCoding, NSCopying> {
    NSMutableArray *highscorelist; 
}

- (void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:highscorelist forKey:@"Highscorelist"];
}

The app will crash with an error message

-[RSHighscore encodeWithCoder:]: unrecognized selector sent to instance 0x573cc20
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[RSHighscore encodeWithCoder:]: unrecognized selector sent to instance 0x573cc20'

I wonder why the error tells about RSHighscore, even if that is 'wrapped'. Does anyone have a good idea?

Stef
  • 593
  • 3
  • 10
  • 23
  • Can you show us your implementation of encodeWithCoder for a HighScoreList. – deanWombourne Nov 30 '10 at 18:33
  • Is `RSHighscoreList` used in the actual code? It's not in the sample. Not using it won't cause problems, but if you do use it, you might also need to implement NSCoding for `RSHighscoreList` as well. – outis Nov 30 '10 at 18:49
  • Note you can format lines as code by indenting them four spaces. The "101\n010" button in the editor toolbar does this for you. ">" is for quoting. Click the orange question mark in the editor toolbar for more information and tips on formatting. – outis Nov 30 '10 at 19:02
  • @outis: Thanks. Was looking for it, but probably did not see the tree due in the forrest. – Stef Nov 30 '10 at 19:24

2 Answers2

10

RSDataStore has an -encodeWithCoder: method, but (according to the error message) RSHighscore doesn't. You need to implement the NSCoding protocol for every class you're serializing.

@implementation RSHighscore
static NSString *const kPlayerName = @"PlayerName";
static NSString *const kPoints = @"Points";

-(id)initWithCoder:(NSCoder *)decoder {
    if ((self=[super init])) {
        playerName = [[decoder decodeObjectForKey:kPlayerName] retain];
        points = [decoder decodeIntegerForKey:kPoints];
    }
    return self;
}
-(void)encodeWithCoder:(NSCoder *)encoder {
    [encoder encodeObject:playerName forKey:kPlayerName];
    [encoder encodeInt:points forKey:kPoints];
}
...

If the base class of RSHighscore is ever changed to something other than NSObject, the -initWithCoder: method might need to be changed to call [super initWithCoder:decoder] rather than [super init]. Alternatively, add <NSCoding> to NSObject and change RSHighscore's -initWithCoder: now.

@interface NSObject (NSCoding)
-(id)initWithCoder:(NSCoder*)decoder;
-(void)encodeWithCoder:(NSCoder*)encoder;
@end

@implementation NSObject (NSCoding)
-(id)initWithCoder:(NSCoder*)decoder {
    return [self init];
}
-(void)encodeWithCoder:(NSCoder*)encoder {}
@end

@implementation RSHighscore
-(id)initWithCoder:(NSCoder *)decoder {
    if ((self=[super initWithCoder:decoder])) {
        playerName = [[decoder decodeObjectForKey:kPlayerName] retain];
        points = [decoder decodeIntegerForKey:kPoints];
    }
    return self;
}
...
outis
  • 75,655
  • 22
  • 151
  • 221
  • 1
    You're welcome. I doubt you need the code sample, but [someone else might](http://msmvps.com/blogs/jon_skeet/archive/2009/02/17/answering-technical-questions-helpfully.aspx), so I stuck it in. – outis Nov 30 '10 at 19:43
4

The class you're going to encode or initWithCoder should conform to <NSCoding> protocol So you just should add this in your interface, otherwise indeed the runtime will not recognize the selector as it's the part of <NSCoding> protocol

Nava Carmon
  • 4,523
  • 3
  • 40
  • 74
  • Did that. *** @interface RSHighscore : NSObject *** but now get a lot of warning *** method definition for -initWithCoder *** not found – Stef Nov 30 '10 at 18:45
  • sorry i didn't mention that it's not an optional method, so you have to implement these methods, that is encodeWithCoder and initWithCoder in each class you're serializing. – Nava Carmon Nov 30 '10 at 19:19