Question

I'm having trouble figuring out how to get the archiving to work in an iOS 5 app. I have a singleton SessionStore that I'd like to retrieve plist data if it exists upon initialization. SessionStore inherits from NSObject and has one ivar, an NSMutableArray *allSessions, which I want to load from the plist file. Here's the SessionStore.m Not sure if the problem is obvious or if you need more info... thanks! Nathan

#import "SessionStore.h"

static SessionStore *defaultStore = nil;

@implementation SessionStore

+(SessionStore *)defaultStore {
    if (!defaultStore) {
        // Load data.plist if it exists
        NSString *pathInDocuments = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"data.plist"];
    NSFileManager *fileManager = [[NSFileManager alloc] init];
    if ([fileManager fileExistsAtPath:pathInDocuments])
        defaultStore = [NSKeyedUnarchiver unarchiveObjectWithFile:pathInDocuments];   
    } else
        defaultStore = [[super allocWithZone:NULL] init]; 

    return defaultStore;
}


+(id)allocWithZone:(NSZone *)zone {
    return [self defaultStore];
}

-(id)init {
    if (defaultStore)
        return defaultStore;

    self = [super init];

    if (self)
        allSessions = [[NSMutableArray alloc] init];

    return self;
}

-(NSMutableArray *)allSessions {
    if (!allSessions) allSessions = [[NSMutableArray alloc] init];
    return allSessions;
}

-(void)setAllSessions:(NSMutableArray *)sessions {
    allSessions = sessions;
}

-(void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:allSessions forKey:@"All Sessions"];
}

-(id)initWithCoder:(NSCoder *)aDecoder {
    self = [SessionStore defaultStore];
    [self setAllSessions:[aDecoder decodeObjectForKey:@"All Sessions"]];
    return self;
}

In AppDelegate.m it saves the plist file upon terminating:

- (void)applicationWillTerminate:(UIApplication *)application
{
    // Save data to plist file
    NSString *pathInDocuments = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"data.plist"];

    [NSKeyedArchiver archiveRootObject:[SessionStore defaultStore] toFile:pathInDocuments];
}
Was it helpful?

Solution

The way I usually do this is to have a data file which I save things to when I need to and then load them back up when the object is initialised. So something like this:

@interface SessionStore
@property (nonatomic, copy) NSMutableArray *allSessions;

- (void)loadData;
- (void)saveData;
@end

static SessionStore *sharedInstance = nil;

static NSString *const kDataFilename = @"data.plist";

@implementation SessionStore

@synthesize allSessions = _allSessions;

#pragma mark -

+ (id)sharedInstance {
    if (sharedInstance == nil)
        sharedInstance = [[self alloc] init];
    return sharedInstance;
}


#pragma mark -

- (id)init {
    if ((self = [super init])) {
        [self loadData];
    }
    return self;
}


#pragma mark -

- (void)loadData {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:kDataFilename];

    NSFileManager *fileManager = [NSFileManager defaultManager];
    if ([fileManager fileExistsAtPath:path]) {
        NSMutableData *theData = [NSData dataWithContentsOfFile:path];
        NSKeyedUnarchiver *decoder = [[NSKeyedUnarchiver alloc] initForReadingWithData:theData];
        self.allSessions = [[decoder decodeObjectForKey:@"allSessions"] mutableCopy];
        [decoder finishDecoding];
    }

    if (!_allSessions) {
        self.allSessions = [[NSMutableArray alloc] initWithCapacity:0];
    }
}

- (void)saveData {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:kDataFilename];

    NSMutableData *theData = [NSMutableData data];
    NSKeyedArchiver *encoder = [[NSKeyedArchiver alloc] initForWritingWithMutableData:theData];

    [encoder encodeObject:_allSessions forKey:@"allSessions"];
    [encoder finishEncoding];

    [theData writeToFile:path atomically:YES];
}

Then whenever I want to, I would call saveData to save the data back to disk. That might be every time allSessions changes, or just once when the app terminates / goes into the background. That would depend on how often allSessions changes and how crucial it is to ensure the data is saved.

Please bear in mind that the code there for the singleton is by no means the best - search around here on StackOverflow for reasoning behind using GCD dispatch_once or similar if you're worried about sharedInstance being racey.

I think this is better than your method because you are trying to serialise the whole singleton object rather than just its contents which I think is a bit easier to understand what's going on and then the singleton itself handles all the loading and saving rather than having your NSKeyedArchiver spill out into the app delegate like you have done.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top