Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Loading a Singleton's state from NSKeyedArchiver

I have a class that I've made into a singleton and am able to save it's state using an NSKeyedArchiver, but I can't wrap my head around pulling it's state back out.

In the function that does the loading I have

Venue *venue = [Venue sharedVenue];
NSData *data = [[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
venue = [unarchiver decodeObjectForKey:@"Venue"];
[unarchiver finishDecoding];

With this code, what does decodeObjectForKey return? It can't really be another instance of Venue and it's not loading in any of the saved values. Before I converted it to a singleton saving and loading was working fine.

like image 417
rob5408 Avatar asked Jul 18 '09 23:07

rob5408


2 Answers

Here's where I think this is going wrong. You're familiar with NSCoding and typically adopting it by making your object codable via encodeWithCoder: and initWithCoder: overrides. What will make this all simpler is that you can still use NSCoding, and NSCoders without overriding those methods. You can encode the state of the shared object without encoding the shared obejct itself. This prevents the awkward question of decoding the shared object.

Here's an example of what I think you could do:

@implementation MySharedObject
+ (id)sharedInstance {
    static id sharedInstance = nil;
    if (!sharedInstance) {
        sharedInstance = [[MyClass alloc] init];
    }
}

- (id)init {
    if ((self = [super init])) {
        NSData *data = /* probably from user defaults */
        if (data) { // Might not have any initial state.
            NSKeyedUnarchiver *coder = [[[NSKeyedUnarchiver alloc] initForReadingWithData:data] autorelease];
            myBoolean = [coder decodeBoolForKey:@"MyBoolean"];
            myObject = [[coder decodeObjectForKey:@"MyObject"] retain];
            [coder finishDecoding];
        }
    }
    return self;
}

- (void)saveState {
    NSMutableData *data = [NSMutableData data];
    NSKeyedArchiver *coder = [[[NSKeyedArchiver alloc] initForWritingWithMutableData:data] autorelease];
    [coder encodeBool:myBoolean forKey:@"MyBoolean"];
    [coder encodeObject:myObject forKey:@"MyObject"];
    [coder finishEncoding]
    // Save the data somewhere, probably user defaults...   
}
@end

Here we have a shared object, and it uses keyed archive to persist configuration, but we don't encode the shared object itself. This avoid that awkward question of decoding a second instance of the singleton class.

like image 179
Jon Hess Avatar answered Oct 04 '22 08:10

Jon Hess


You need to do the loading in the singleton itself what is going on here is you create the single, you assign an lval to the singleton, you then create a new object and reassign the lval to that new object WITHOUT modifying the singleton. In other words:

//Set venue to point to singleton
Venue *venue = [Venue sharedVenue];

//Set venue2 to point to singleton
Venue *venue2 = [Venue sharedVenue];

NSData *data = [[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];

//Set venue to unarchived object (does not change the singleton or venue2)
venue = [unarchiver decodeObjectForKey:@"Venue"];
[unarchiver finishDecoding];

What you want to do is deal with this in the sharedVenue. There are a couple of ways people do singletons, so I can't be sure what you are doing, but lets assume sharedVenue currently looks something like this:

static Venue *gSharedVenue = nil;

- (Venue *) sharedVenue {
  if (!gSharedVenue) {
    gSharedVenue = [[Venue alloc] init];
  }

  return gSharedVenue;
}

Assuming that is the case you want to change it to load the object into the global backing the singleton:

static Venue *gSharedVenue = nil;

- (Venue *) sharedVenue {
  if (!gSharedVenue) {
    NSData *data = [[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    [data release];

    gSharedVenue = [unarchiver decodeObjectForKey:@"Venue"];
    [unarchiver finishDecoding];
    [unarchiver release];
  }

  if (!gSharedVenue) {
    gSharedVenue = [[Venue alloc] init];
  }

  return gSharedVenue;
}

Obviously you need to somehow convey the actual path to archived object file.

EDIT BASED ON COMMENT:

Okay, if you are using the alloc based singleton you need to deal with this in the classes init method:

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

  if (self) {
    NSData *data = [[NSMutableData alloc] initWithContentsOfFile:[self dataFilePath]];
    NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
    [data release];

    Venue *storedVenue = [unarchiver decodeObjectForKey:@"Venue"];
    [unarchiver finishDecoding];
    [unarchiver release];

    if (storeVenue) {
       [self release];
       self = [storedVenue retain];
    }

  }

  return self;
}
like image 35
Louis Gerbarg Avatar answered Oct 04 '22 09:10

Louis Gerbarg