NSUserDefaults не может сохранять и читать мои пользовательские объекты

У меня есть класс Notification, который реализует протокол NSCoding.
У меня есть массив уведомлений. Я пытаюсь сохранить уведомления с помощью NSUserDefaults. В моем делегате уведомлений моего приложения есть NSMutableArray, который содержит объекты Notification. Это мой делегат приложения:

+ (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"];
}

В классе Notification текст и заголовок имеют тип NSString (оба для чтения), а дата имеет тип NSDate (также имеет свойство readwrite). Вот как я реализую протокол NSCoding:

- (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;
}

Итак, у меня есть эти проблемы:

  1. Когда приложение завершается, я получаю EXC_BAD_ACCESS в классе уведомлений, когда пытаюсь кодировать текст с помощью NSKeyedArchiver;
  2. Уведомления не сохраняются, и при запуске приложения массив всегда равен нулю.

Обновление: с помощью дополнительной отладки я обнаружил, где происходит сбой приложения. Можно увидеть больше кода (я использую табличное представление для отображения данных):

- (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];
}

Последний вызываемый метод таков:

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

Вероятно, проблема в том, что данные каким-то образом повреждены, и значение, возвращаемое этим методом, недействительно. В любом случае приложение не падает в этом методе, а после этого метода.
Если я загружаю два объекта из пользовательских значений по умолчанию, только один объект загружается перед сбоем (поэтому метод вызывается один раз).
Однако я все еще не могу понять настоящую причину сбоя.

Больше кода:

- (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 и removeNotification запускаются кнопками.

РЕДАКТИРОВАТЬ: я обнаружил, что не использую ARC, но даже если я включу его, приложение вылетает.


person Ramy Al Zuhouri    schedule 29.10.2012    source источник


Ответы (3)


Возможно, вам придется вызвать [NSUserDefaults synchronize], так как это не произойдет автоматически, когда приложение внезапно завершит работу:

- (void) applicationWillTerminate:(NSNotification *)notification
{
    NSUserDefaults* defaults=[NSUserDefaults standardUserDefaults];
    NSData* notificationsData=[NSKeyedArchiver archivedDataWithRootObject: notifications];
    [defaults setObject: notificationsData forKey: @"notificationsData"];
    [defaults synchronize];
}
person Nathan Villaescusa    schedule 29.10.2012
comment
Да, но он все равно вылетает, есть еще проблема. - person Ramy Al Zuhouri; 29.10.2012
comment
Что произойдет, если вы замените NSData* notificationsData=[NSKeyedArchiver archivedDataWithRootObject: notifications]; на NSData* notificationsData=[NSKeyedArchiver archivedDataWithRootObject:@[]];? - person Nathan Villaescusa; 29.10.2012
comment
В этом случае приложение работает гладко, без каких-либо исключений. Но, конечно, поскольку массив пуст, я не могу восстановить уведомления от предыдущего выполнения. Я сомневаюсь, что что-то не так в том, как я реализую протокол. - person Ramy Al Zuhouri; 29.10.2012

В соответствии:

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

@"data" не соответствует ключу энкодера. Вы действительно хотите:

date=[aDecoder decodeObjectForKey: @"date"];
person thelaws    schedule 29.10.2012
comment
Спасибо, сейчас заменил. Но программа все равно вылетает. - person Ramy Al Zuhouri; 29.10.2012
comment
Вы полностью удалили приложение? Старые пользовательские настройки по умолчанию могут быть все еще там. - person thelaws; 29.10.2012
comment
Я использовал метод resetStandardUserDefaults NSUserDefault, но приложение по-прежнему падает. - person Ramy Al Zuhouri; 29.10.2012
comment
Код, который вы вставили в обновление, мне подходит. Когда вы отлаживаете, obj всегда равен нулю? - person thelaws; 30.10.2012
comment
obj не равен нулю, это допустимый объект. Но если я попытаюсь зарегистрировать его класс, я получу исключение. Если я попытаюсь отладить заголовок и дату метода initWithCoder, все в порядке, но посмотрите, что он говорит о тексте: (lldb) печатать текст (NSString *) $3 = 0x000000010050e080 @‹переменная не NSString› - person Ramy Al Zuhouri; 30.10.2012
comment
Извините, я не вижу проблемы, ваш код добавления/удаления тоже выглядит нормально. Может быть, добавить исключение, которое вы получаете при запуске приложения? - person thelaws; 30.10.2012
comment
EXC_BAD_ACCESS (код = 1, адрес = 0x13) в objc_msgSend, и он показывает код сборки. - person Ramy Al Zuhouri; 30.10.2012
comment
давайте продолжим это обсуждение в чате - person thelaws; 30.10.2012

Вы не могли догадаться, что было не так: моя беда, я забыл включить ARC, и некоторые объекты были освобождены, когда они не должны.

person Ramy Al Zuhouri    schedule 30.10.2012