0

I have a class Notification that implements the NSCoding protocol.
I have an array of notifications.I am trying to save the notifications with NSUserDefaults.In my app delegate notifications is a NSMutableArray that contains the Notification objects.That's my app delegate:

+ (void) initialize
{
    NSUserDefaults* defaults=[NSUserDefaults standardUserDefaults];
    [defaults registerDefaults: [NSDictionary dictionaryWithObject: [NSKeyedArchiver archivedDataWithRootObject: [NSArray array]] forKey: @"notificationsData"]];
}


- (id) init
{
    self=[super init];
    if(self)
    {
        NSUserDefaults* defaults=[NSUserDefaults standardUserDefaults];
        NSData* notificationsData=[defaults objectForKey: @"notificationsData"];
        notifications= [[NSKeyedUnarchiver unarchiveObjectWithData: notificationsData]mutableCopy];
    }
    return self;
}

- (void) applicationWillTerminate:(NSNotification *)notification
{
    NSUserDefaults* defaults=[NSUserDefaults standardUserDefaults];
    NSData* notificationsData=[NSKeyedArchiver archivedDataWithRootObject: notifications];
    [defaults setObject: notificationsData forKey: @"notificationsData"];
}

In the Notification class text and title are of type NSString (both readwrite), and date is of type NSDate (also this has readwrite property).This is how I implement the NSCoding protocol:

- (void) encodeWithCoder:(NSCoder *)aCoder
{
    [aCoder encodeObject: date forKey: @"date"];
    [aCoder encodeObject: title forKey: @"title"];
    [aCoder encodeObject: text forKey: @"text"];
}

- (id) initWithCoder:(NSCoder *)aDecoder
{
    self=[super init];
    if(self)
    {
        date=[aDecoder decodeObjectForKey: @"data"];
        title=[aDecoder decodeObjectForKey: @"title"];
        text=[aDecoder decodeObjectForKey: @"text"];
    }
    return self;
}

So I have these problems:

  1. When the application terminates I get EXC_BAD_ACCESS in the Notification class, when I try to encode text with NSKeyedArchiver;
  2. The notifications aren't saved and the array is always long zero when the application starts.

Update: With more debug I discovered where the application crashes.There is more code to see (I'm using a table view to display the data):

- (NSInteger) numberOfRowsInTableView:(NSTableView *)tableView
{
    return [notifications count];
}

- (id) tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
    // Debug
    id obj=[[notifications objectAtIndex: row] valueForKey: [tableColumn identifier]];
    Class class=[obj class];
    // What you see above is just for debug purposes
    return [[notifications objectAtIndex: row] valueForKey: [tableColumn identifier]];
}

- (void) tableViewSelectionDidChange:(NSNotification *)notification
{
    NSInteger row=[tableView selectedRow];
    if(row >= 0 && row< [notifications count])
        [removeButton setEnabled: YES];
    else
        [removeButton setEnabled: NO];
}

The last method called is this:

- (id) tableView:(NSTableView *)tableView objectValueForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row;

Probably the problem is that the data is somehow corrupted and the value returned by this method is not valid.Anyway the app doesn't crash in this method, but after this method.
If I load two objects from user defaults, only one object gets loaded before crashing (so the method gets called once).
However I'm still unable to get the real reason of the crash.

More code:

- (IBAction) addNotification :(id)sender
{
    Notification* notification=[[Notification alloc]init];
    [notification setDate: [datePicker dateValue]];
    [notification setText: [textView string]];
    [notifications addObject: notification];
    [tableView reloadData];
}

- (IBAction)removeNotification:(id)sender
{
    [notifications removeObjectAtIndex: [tableView selectedRow]];
    [tableView reloadData];
}

addNotification and removeNotification are both triggered by buttons.

EDIT: I discovered that I wasn't using ARC, but even if I turn it on the app crashes.

Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187

3 Answers3

2

In the line:

date=[aDecoder decodeObjectForKey: @"data"];

@"data" doesn't match the encoder key. You really want:

date=[aDecoder decodeObjectForKey: @"date"];
thelaws
  • 7,991
  • 6
  • 35
  • 54
1

You might need to call [NSUserDefaults synchronize] since it will not happen automatically when the application suddenly exits:

- (void) applicationWillTerminate:(NSNotification *)notification
{
    NSUserDefaults* defaults=[NSUserDefaults standardUserDefaults];
    NSData* notificationsData=[NSKeyedArchiver archivedDataWithRootObject: notifications];
    [defaults setObject: notificationsData forKey: @"notificationsData"];
    [defaults synchronize];
}
Nathan Villaescusa
  • 17,331
  • 4
  • 53
  • 56
  • Yes, but it crashes anyway, there's still a problem. – Ramy Al Zuhouri Oct 29 '12 at 18:04
  • 1
    What happens if you replace `NSData* notificationsData=[NSKeyedArchiver archivedDataWithRootObject: notifications];` with `NSData* notificationsData=[NSKeyedArchiver archivedDataWithRootObject:@[]];`? – Nathan Villaescusa Oct 29 '12 at 18:21
  • This case the application runs smoothly, without any exception thrown.But of course since the array is empty, I can't recover the notifications from the previous execution.I doubt that there is something wrong in how I implement the protocol. – Ramy Al Zuhouri Oct 29 '12 at 18:31
0

You couldn't guess what was wrong: my bad, I forgot to enable ARC and some objects were released when they shouldn't.

Ramy Al Zuhouri
  • 21,580
  • 26
  • 105
  • 187