Search code examples
iosobjective-cnskeyedarchivernskeyedunarchiver

Failing to set / retrieve data using NSKeyedArchiver / NSKeyedUnarchiver on file


I am trying to save some persistent data on an iOS app using NSKeyedArchiver to write to a file, and I would like to retrieve this data later using NSKeyedUnarchiver. I created a very basic application to test some code, but with no success. Here are the methods that I am using:

- (void)viewDidLoad
{
    [super viewDidLoad];

    Note *myNote = [self loadNote];

    myNote.author = @"MY NAME";

    [self saveNote:myNote]; // Trying to save this note containing author's name

    myNote = [self loadNote]; // Trying to retrieve the note saved to the file

    NSLog(@"%@", myNote.author); // Always logs (null) after loading data
}

-(NSString *)filePath
{
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,  NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *filePath = [documentsDirectory stringByAppendingPathComponent: @"myFile"];
    return filePath;
}

-(void)saveNote:(Note*)note
{
    bool success = [NSKeyedArchiver archiveRootObject:note toFile:[self filePath]];
    NSLog(@"%i", success); // This line logs 1 (success)
}

-(Note *)loadNote
{
    return [NSKeyedUnarchiver unarchiveObjectWithFile:[self filePath]];
}

The class that I am using to test this code is as follows:

Note.h

#import <Foundation/Foundation.h>

@interface Note : NSObject <NSCoding>

@property NSString *title;
@property NSString *author;
@property bool published;

@end

Note.m

#import "Note.h"

@implementation Note

-(id)initWithCoder:(NSCoder *)aDecoder
{
    if (self = [super init])
    {
        self.title = [aDecoder decodeObjectForKey:@"title"];
        self.author = [aDecoder decodeObjectForKey:@"author"];
        self.published = [aDecoder decodeBoolForKey:@"published"];
    }
    return self;
}

-(void)encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject:self.title forKey:@"title"];
    [aCoder encodeObject:self.author forKey:@"author"];
    [aCoder encodeBool:self.published forKey:@"published"];
}

@end

I have seen similar examples using NSUserDefaults (https://blog.soff.es/archiving-objective-c-objects-with-nscoding), but I would like to save this data to a file, because as far as I know NSUserDefaults is used mainly to store user preferences, and not general data. Am I missing something? Thanks in advance.


Solution

  • Think about what happens the first time your app runs and you call your loadNote: method and there isn't anything saved yet.

    The line:

    Note *myNote = [self loadNote];
    

    will result in myNote being nil since nothing was loaded. Now think how that cascades through the rest of your code.

    You need to handle this initial case of there being no saved data.

    Note *myNote = [self loadNote];
    if (!myNote) {
        myNote = [[Note alloc] init];
        // Setup the initial note as needed
        myNote.title = ...
    }