Challenge: Savind and Loading (My solution and questions)


#1

I started by customizing the initWithCoder: method and adding two observers. One that listened for applicationWillTerminate: and one for applicationDidEnterBackground:.

[[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(saveChanges) 
                                                     name:UIApplicationWillTerminateNotification
                                                   object:[UIApplication sharedApplication]];
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(saveChanges) 
                                                     name:UIApplicationDidEnterBackgroundNotification
                                                   object:[UIApplication sharedApplication]];

This View will never be released, so it won’t really matter. But for best practice you should also remove the observer once it gets deallocated.

[code]

  • (void)dealloc {
    [linesInProcess release];
    [completeLines release];

    [[NSNotificationCenter defaultCenter] removeObserver:self];

    [super dealloc];
    }[/code]

A method called saveChanges is called so we’ll create that now. This method will use an NSKeyedArchiver to write our lines to file.

- (void)saveChanges {
    [NSKeyedArchiver archiveRootObject:completeLines toFile:[self shapesArchivePath]];
    NSLog(@"Write %i completeLines to File", [completeLines count]);
}

But in order to actually save our lines with an NSKeyedArchiver our objects will have to conform to the NSCoding protocol. So add this in Line.h and add the two necessary functions in Line.m (initWithCoder and encoderWithCoder)

- (id)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    
    if(self) {
        NSValue *beginValue = [aDecoder decodeObjectForKey:@"begin"];
        [self setBegin:[beginValue CGPointValue]];
    
        NSValue *endValue = [aDecoder decodeObjectForKey:@"end"];
        [self setEnd:[endValue CGPointValue]];
    }
    
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:[NSValue valueWithCGPoint:begin] forKey:@"begin"];
    [aCoder encodeObject:[NSValue valueWithCGPoint:end] forKey:@"end"];
}

Now that our lines are being saved we’ll need to load them again when we start our application. We do this by creating a method that will initialize our completeLines NSMutableArray for us. Don’t forget to add it to your interface file.

[code]- (NSMutableArray *)getCompleteLines {
if(!completeLines) {
completeLines = [[NSKeyedUnarchiver unarchiveObjectWithFile:[self shapesArchivePath]] retain];

    if([completeLines count] > 0) {
        NSLog(@"%i lines found in archive!", [completeLines count]);
        [self setNeedsDisplay];   
    }
}

if(!completeLines) {
    completeLines = [[NSMutableArray alloc] init];
}

return completeLines;

}[/code]

For best practice we will also use a FileHelper like the one we created for whereAmI and add a method to return the path to our completeLines archive.

  • (NSString *)shapesArchivePath {
    return pathInDocumentDirectory(@“shapes.data”);
    }

———

My questions for the more experienced developers here. This is a view, so storing and recalling data shouldn’t be done here. Should I have made a controller and a model here where the model saved/loaded the completeLines?
And for my second question: I have listened to the UIApplication notifications being sent here, would it have been better to use the methods provided in my App Delegate and have that class call my saveChanges method via its rootViewController?

For example:


#2

Technically it’s the correct thing to do to maintain proper MVC(S) separation, but not really necessary for such a simple application. If you had intentions of expanding the app, saving and loading separate files for example, or manipulating the data separately from the view, then abstracting the lines to a LineStore in the same way the PossessionStore is abstracted away in Homepwner would be ideal.


#3

Ziggie,
I am fairly inexperienced in this field, so please forgive me if this is a stupid question, but how did you know to wrap the point in a value object? I tried using the method encodeCGPointForKey: and decodeCGPointForKey: and it didn’t save. I don’t know why.

Any feedback is appreciated


#4

Also, Ziggie,
I tried using your solution, but I still get the same result as I mentioned in my previous post. It doesn’t crash or anything, but even your solution still wouldn’t save.

Again, any feedback is appreciated


#5

Hello,

to make the ‘Line’ class conform to the NSCoding protocol, you can also use ‘encodeCGPoint’ and 'decodeCGPointForKey", this directly encodes and decodes CGPoint, without the hassle of passing through NSValue.

  • (void)encodeWithCoder:(NSCoder *)encoder
    {
    [encoder encodeCGPoint:begin forKey:@“begin”];
    [encoder encodeCGPoint:end forKey:@“end”];
    }

  • (id)initWithCoder:(NSCoder *)decoder
    {
    self = [super init];

    if(self)
    {
    // for each instance variable that is archived, we decode it,
    // and pass it our setters (where it is retained)
    [self setBegin:[decoder decodeCGPointForKey:@“begin”]];
    [self setEnd:[decoder decodeCGPointForKey:@“end”]];
    }

    return self;
    }