Question

So I'm trying to write a NSMutableArray of custom objects (a "Course" representing a college course for a Course Planner app) to a file when my application terminates and then read that array from the file into the relevant ViewController that will make use of the data when the application starts up.

Here is the relevant code:

CoursesAppDelegate.m:

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {    

    // Override point for customization after application launch.
    coursesViewController = [[SampleHomeScreen alloc] initWithNibName:@"SampleHomeScreen" bundle:nil];

    NSString *filePath = [self dataFilePath];
    if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) {
        [coursesViewController setCourses:[NSKeyedUnarchiver unarchiveObjectWithFile: filePath]];
    }

    UIApplication *app = [UIApplication sharedApplication];
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillTerminate:)name:UIApplicationWillTerminateNotification object:app];

    [window addSubview:coursesViewController.view];
    [window makeKeyAndVisible];

    return YES;
}

- (NSString *)dataFilePath {
    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documentsDirectory = [paths objectAtIndex:0];
    NSString *path = [documentsDirectory stringByAppendingPathComponent:@"data.plist"];
    NSLog(@"%@", path);
    return path;
}

/**
 applicationWillTerminate: saves changes in the application's managed object context before the application terminates.
 */
- (void)applicationWillTerminate:(UIApplication *)application {
    NSLog(@"%@", [coursesViewController courses]);
    [NSKeyedArchiver archiveRootObject:[coursesViewController courses] toFile:[self dataFilePath]];
}

Course.h:

@interface Course : NSObject <NSCoding> {
    NSString *name; //e.g. ECS 189H
    double grade, totalWeight; //course grade in %
    NSMutableArray *list;
}

@property (nonatomic, retain) NSString *name;
@property (nonatomic) double grade, totalWeight;
@property (nonatomic, retain) NSMutableArray *list;

-(Course *)initWithName:(NSString *)courseName;

@end

Course.m:

@implementation Course

@synthesize name, grade, totalWeight, list;

-(Course *)initWithName:(NSString *)courseName {
    name = [courseName retain];
    grade = -1.0;
    totalWeight = 0.0;
    list = [[NSMutableArray alloc] init];
    [super init];
    return self;
}

-(Course *)initWithCoder:(NSCoder *)aDecoder {
    self.name = [aDecoder decodeObjectForKey:@"name"];
    self.grade = [aDecoder decodeDoubleForKey:@"grade"];
    self.totalWeight = [aDecoder decodeDoubleForKey:@"totalWeight"];
    self.list = [aDecoder decodeObjectForKey:@"list"];
    [super init];
    return self;
}

- (void) encodeWithCoder: (NSCoder *)coder
{
    [coder encodeObject:name forKey:@"name"];   
    [coder encodeDouble:grade forKey:@"grade"];
    [coder encodeDouble:totalWeight forKey:@"totalWeight"];
    [coder encodeObject:list forKey:@"list"];
}

-(void)dealloc {
    [name release];
    [list release];
    [super dealloc];
}

@end

[coursesViewController courses] is the NSMutableArray that holds the course objects. I know for a fact that it holds valid data.

So the problems are, 1: The application saves to data.plist ONLY when I run it from xcode (ie click "compile and run" in xcode). 2: It loads data from the plist, but all that gets saved are the course names and the default values for grade and totalWeight (-1 and 0 respectively). So really they are saved as though initWithName was called on them first.

This is my first real delve into a fairly advanced iOS application, so as I am a newbie to this, I may have left out some important info. If that is the case, please let me know and I will update the question.

Thanks! -HT

p.s. If it is relevant, I have doNotRunInBackground in the info.plist set to true.

Was it helpful?

Solution

Your are trying to set values in your object before it's been initialized. And initialization will then reset your values.

-(Course *)initWithName:(NSString *)courseName {
    name = [courseName retain];              // <- Accessing ivar before self is initialized
    grade = -1.0;                            // <- Accessing ivar before self is initialized
    totalWeight = 0.0;                       // <- Accessing ivar before self is initialized
    list = [[NSMutableArray alloc] init];    // <- Accessing ivar before self is initialized
    [super init];                            // initialization resets your values !!!!
    return self;
}

Additionally you are ignoring super's init return value, which will work fine 98 % of all cases, but I recommend to always use a proper initialization scheme:

- (id)init {
    if (self = [super init]) {
        // It's save to access ivars here
    }
    return self
}

In Cocoa an init method may return a different object, then the one that was allocated. So you must assign self to the super's init.

So, your init should look like:

-(Course *)initWithName:(NSString *)courseName {
    if (self = [super init]) {
        name = [courseName retain];
        grade = -1.0;
        totalWeight = 0.0;
        list = [[NSMutableArray alloc] init];
    }
    return self;
}

The same applies to initWithCoder:(NSCoder *)coder.

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