tags:

views:

90

answers:

3

Hi guys,

I've been trying to teach myself how to use the Archiving/Unarchiving methods of NSCoder, but I'm stumped.

I have a Singleton class that I have defined with 8 NSInteger properties. I am trying to save this object to disk and then load from disk as needed.

I've got the save part down and I have the load part down as well (according to NSLogs), but after my "initWithCoder:" method loads the object's properties appropriately, the "init" method runs and resets my object's properties back to zero.

I'm probably missing something basic here, but would appreciate any help!

My class methods for the Singleton class:

+ (Actor *)shareActorState
{
    static Actor *actorState;

    @synchronized(self) {
        if (!actorState) {
            actorState = [[Actor alloc] init];
        }
    }

    return actorState;  
}


-(id)init
{
    if (self = [super init]) {

        NSLog(@"New Init for Actor started...\nStrength: %d", self.strength);
    }

    return self;
}

-(id)initWithCoder:(NSCoder *)coder
{

    if (self = [super init]) {

        strength = [coder decodeIntegerForKey:@"strength"];
        dexterity = [coder decodeIntegerForKey:@"dexterity"];
        stamina = [coder decodeIntegerForKey:@"stamina"];
        will = [coder decodeIntegerForKey:@"will"];
        intelligence = [coder decodeIntegerForKey:@"intelligence"];
        agility = [coder decodeIntegerForKey:@"agility"];
        aura = [coder decodeIntegerForKey:@"aura"];
        eyesight = [coder decodeIntegerForKey:@"eyesight"];
        NSLog(@"InitWithCoder executed....\nStrength: %d\nDexterity: %d", self.strength, self.dexterity);
        [self retain];

    }

    return self;                 
}

-(void) encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeInteger:strength forKey:@"strength"];
    [encoder encodeInteger:dexterity forKey:@"dexterity"];
    [encoder encodeInteger:stamina forKey:@"stamina"];
    [encoder encodeInteger:will forKey:@"will"];
    [encoder encodeInteger:intelligence forKey:@"intelligence"];
    [encoder encodeInteger:agility forKey:@"agility"];
    [encoder encodeInteger:aura forKey:@"aura"];
    [encoder encodeInteger:eyesight forKey:@"eyesight"];
    NSLog(@"encodeWithCoder executed....");
}


-(void)dealloc
{
    //My dealloc stuff goes here
    [super dealloc];
}

I'm a noob when it comes to this stuff and have been trying to teach myself for the last month, so forgive anything obvious.

Thanks for the help!

A: 

I'm not sure that it makes sense to have a singleton class that is archived/unarchived. When you do Actor *actor = [Actor shareActorState]; you're accessing the shared instance. If you then archive that NSData *data = [NSKeyedArchiver archivedDataWithRootObject:actor], you'll be saving the correct thing.

What are you intending when you unarchive, though? Actor *newactor = [NSKeyedUnarchiver unarchiveObjectWithData:data]; creates a new actor. It's in a new area of memory from your shared actor. This newactor is different from the singleton, [Actor shareActorState], and to violates the design pattern since you now have more than one object.

More likely, you'd want to implement two instance methods on your Actor class. They would be:

- (NSData *)archiveStateToData;
- (void)unarchiveStateFromData:(NSData *)data;

Which would actually update the values in the instance (using archiving). If you're trying to learn the NSCoding protocol and how to archive/unarchive data, I would recommend not doing it with a singleton.

wbyoung
A: 

While I agree with @wbyoung that you probably shouldn't be archiving/unarchiving a singleton (unless you're going through NSUserDefaults), if you were to do this I believe it would work by just changing

-(id)initWithCoder:(NSCoder *)coder
{

    if (self = [super init]) {

to

-(id)initWithCoder:(NSCoder *)coder
{

    if (self = [self shareActorState]) {

(oh and I would rename shareActorState to sharedActorState, or better yet sharedActor)

Jared P
This would work, but makes little sense since you're clobbering the shared actor when unarchiving. Still, it should work.
wbyoung
well yes -- although odd, given that this is a singleton pattern, it would kind of make sense to replace it entirely. That being said, @Felixyz does this more elegantly, I think.
Jared P
A: 

I use the following template all the time and find it very useful. The loading and saving of the singleton's state is encapsulated and all you have to do is to ask for the shared instance. You might want to make persistToStorage public and call it from the app delegate's applicationWillTerminate: method.

(You might want to make this more thread safe.)

static Actor*       SharedActor;

+(Actor*)sharedActor
{
    if (SharedActor)
        return SharedActor;

    SharedActor = [[NSKeyedUnarchiver unarchiveObjectWithFile:[self actorDataFileName]]retain];
    if (!SharedActor)
        SharedActor = [[Actor alloc]init];

    return SharedActor;
}

+(NSString*)actorDataFileName
{
    NSString* path = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    return [path stringByAppendingPathComponent:@"actor.dat"];
}

-(BOOL)persistToStorage
{
    return [NSKeyedArchiver archiveRootObject:self toFile:[Actor userDataFileName]];
}

-(void) encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeInteger:strength forKey:@"strength"];
    . . .
}

-(id)initWithCoder:(NSCoder *)decoder 
{
    if (self = [super init])
    {
        strength = [decoder decodeIntegerForKey:@"strength"];
        . . .
    }
}

A couple of things:

  • Don't do this: [self retain];
  • There is nothing strange about archiving a singleton...
Felixyz
Okay - I am trying the method proposed above (I love how clean your code is, btw!). When I try to call the -(Bool)persistToStorage method in an IBAction with: Actor *playerCharacter = [[Actor sharedActor] persistToStorage]; - I am getting the warning "Initialization makes pointer from integer without a cast". What am I missing? Thanks!
Zigrivers
persistToStorage returns a BOOL, you're trying to instantiate an Actor (which is a pointer type). If you just want the shared instance, do: Actor* actor [Actor sharedActor]; If you want to save the state: [[Actor sharedActor]persistToStorage]; (Another thing: you should prefix class names with some fairly unique string, so you'd have XYActor or something. It's common Objective-C practice.) I can't take much credit for the code, btw. Pilfered most of it from other people.
Felixyz
Another thing: this is a lazy person's singleton. You could/should prevent users from making their own instances (although sometimes that's fine...) and as I said it's not thread safe.
Felixyz