Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NSUserDefaults vs. NSKeyedArchiver

I think this is a beginner's question about data management.

I have an app which loads and saves different txt files. I would like to give each txt file certain characteristics which are not saved in the txt file, such as name, style, colour etc.

What I did so far was to have a NSMutableArray *myLibrary where I would save all these references. The array looks like this:

  • for each txt file one object is added to myLibrary
  • each object holds several NSStrings, NSUInteger, BOOLs, and some NSMutableArrays
  • in my subclass for these objects, I work with

    -(id)initWithCoder:(NSCoder *)aDecoder; -(void)encodeWithCoder:(NSCoder *)aCoder;

Now, my big question is what the best and safest way is to persevere this data. So far, I've worked with NSKeyedArchiver, but users have reported data loss and there has been one weird case in which the iTunes sync allegedly caused data to be messed up. Is NSUserDefaults any saver? I am saving the myLibrary.dat every time the user has made a change, so I don't really understand why it shouldn't be 100% save. Any advice / suggestions / explanations would be very much appreciated!

Here is the code with which I write and read myLibrary.Dat:

   -(NSString*)documentsDirectory
{
    NSLog(@"documentsDirectory");
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); 
    NSString *documentsDirectory = [paths objectAtIndex:0]; // Get documents directory
    return documentsDirectory;
}

-(void)saveLibraryDat {

    NSLog(@"Saving myLibrary.dat...");
    NSString *filePath = [[self documentsDirectory] stringByAppendingPathComponent:@"myLibrary.dat"];
    BOOL succeed = [[NSKeyedArchiver archivedDataWithRootObject:myLibrary] writeToFile:filePath atomically:YES];
    if (!succeed){ // do something  }

}


-(void)loadLibraryDat {

    NSLog(@"loadLibraryDat");
    NSString *filePath = [[self documentsDirectory] stringByAppendingPathComponent:@"myLibrary.dat"];

    self.myLibrary = [NSKeyedUnarchiver unarchiveObjectWithFile:filePath];

    if (self.myLibrary == nil) {
        NSLog(@"myLibrary.dat empty... set up new one");
        [self setupLibrary];
    } else { NSLog(@"Loading myLibrary.dat successful."); }

}

EDIT: My coder methods look like this:

 - (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        self.titleName = [aDecoder decodeObjectForKey:@"titleName"];
        self.fileName = [aDecoder decodeObjectForKey:@"fileName"];
        self.fileFont = [aDecoder decodeObjectForKey:@"fileFont"];
 self.tabTitles = [aDecoder decodeObjectForKey:@"tabTitles"];
   }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:titleName forKey:@"titleName"];
    [aCoder encodeObject:fileName forKey:@"fileName"];
    [aCoder encodeObject:fileFont forKey:@"fileFont"];
    [aCoder encodeObject:tabTitles forKey:@"tabTitles"];
}

@end

in the header these are defined as:

NSString *titleName;
NSString *fileName;
NSString *fileFont;
NSMutableArray *tabTitles;
like image 583
n.evermind Avatar asked Oct 11 '22 09:10

n.evermind


2 Answers

Don't user NSUserDefaults. It is more for default settings like should I display window X on startup. NSUserDefaults is not designed for program data. You should be using NSKeyArchiver as you are doing. The bug is most likely in the objects that myLibrary contains.

You should take a look at(or post) the functions:

- (id)initWithCoder:(NSCoder *)coder 
- (void)encodeWithCoder:(NSCoder *)coder

That is most likely where your issue lies.

like image 103
Matthieu Cormier Avatar answered Oct 17 '22 13:10

Matthieu Cormier


Users are able to access the documents directory under iOS5 and under any version if they use jailbroken phones. Save your files to ~/Library/Application Support/appname instead.

NSMutableString *path = [[NSMutableString alloc] init];
NSArray *paths = NSSearchPathForDirectoriesInDomains (NSApplicationSupportDirectory, NSUserDomainMask, YES);
[path appendString: [paths objectAtIndex: 0]];
[path appendString: @"/YOURAPPNAME"];

edit

The code you posted works fine and there shouldn't be any problems if your array contains data types supported by NSCoding. As a matter of style, I would use a constant as key to avoid silly typos in the keys. eg: NSString *const kTitleName = @"kTitleName"; above your @implementation section.

like image 24
Jano Avatar answered Oct 17 '22 13:10

Jano