0

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];
}
Nathan W
  • 15
  • 5
  • It freezes when trying to load the plist file. "EXC_BAD_ACCESS" on the line: NSString *pathInDocuments = [[NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) lastObject] stringByAppendingPathComponent:@"data.plist"]; – Nathan W Feb 06 '12 at 18:39

1 Answers1

0

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.

mattjgalloway
  • 34,792
  • 12
  • 100
  • 110
  • Wow, thank you so much. Your way seems much cleaner, as you described with the methods for saving and loading being called by the singleton. I'll have to look up some of the methods you called since I'm a beginner and I don't recognize them, and then I'll post an update if I can get it to work. Thanks! – Nathan W Feb 06 '12 at 20:15
  • It didn't work, after trying multiple variations. I'm going to hit the books and maybe try testing out the archiving process on a sandbox to see if I can pinpoint the problem. Thanks for your help, though, I appreciate the effort. – Nathan W Feb 11 '12 at 23:41
  • @NathanW What didn't work? I do that exact thing in a singleton I use and it works absolutely fine. What's going wrong for you? – mattjgalloway Feb 11 '12 at 23:56
  • To be honest I put it on hold for a while. I imagine there may be some conflict somewhere else in my code, even though it works ok when I don't try loading from a plist. You did answer my question though, and I'll likely come back to this bookmark the next time I try to do this! Thanks again. – Nathan W Feb 27 '12 at 06:07