Почему NSUserDefaults не хочет сохранять мой NSDictionary?

Я использую KVC для сериализации NSObject и пытаюсь сохранить его в NSUserDefaults, что дает мне Attempt to insert non-property value, когда я пытаюсь сохранить NSDictionary.

Ниже приведены свойства рассматриваемого объекта MyClass:

@interface MyClass : NSObject
@property (copy,nonatomic) NSNumber* value1;
@property (copy,nonatomic) NSNumber* value2;
@property (copy,nonatomic) NSString* value3;
@property (copy,nonatomic) NSString* value4;
@property (copy,nonatomic) NSString* value5;
@property (copy,nonatomic) NSString* value6;
@property (copy,nonatomic) NSString* value7;
@property (copy,nonatomic) NSString* value8;
@end

Когда пришло время сохранить MyClass, это происходит здесь:

-(void)saveMyClass
{
  NSArray* keys = [NSArray arrayWithObjects:
                @"value1",
                @"value2",
                @"value3",
                @"value4",
                @"value5",
                @"value6",
                @"value7",
                @"value8",
                nil];
  NSDictionary* dict = [self dictionaryWithValuesForKeys:keys];
  for( id key in [dict allKeys] )
  {
    NSLog(@"%@ %@",[key class],[[dict objectForKey:key] class]);
  }
  NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
  [defaults setObject:dict forKey:[NSString stringWithString:kMyClassKey]];
  [defaults synchronize];
}

который производит этот вывод:

2012-02-23 19:35:27.518 MyApp[10230:40b] __NSCFConstantString __NSCFNumber
2012-02-23 19:35:27.519 MyApp[10230:40b] __NSCFConstantString __NSCFNumber
2012-02-23 19:35:27.519 MyApp[10230:40b] __NSCFConstantString __NSCFString
2012-02-23 19:35:27.519 MyApp[10230:40b] __NSCFConstantString __NSCFString
2012-02-23 19:35:27.520 MyApp[10230:40b] __NSCFConstantString __NSCFString
2012-02-23 19:35:27.520 MyApp[10230:40b] __NSCFConstantString __NSCFString
2012-02-23 19:35:27.520 MyApp[10230:40b] __NSCFConstantString __NSCFString
2012-02-23 19:35:27.520 MyApp[10230:40b] __NSCFConstantString NSNull
2012-02-23 18:38:48.489 MyApp[9709:40b] *** -[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '{
    value1 = "http://www.google.com";
    value2 = "MyClassData";
    value3 = 8;
    value4 = "<null>";
    value5 = "http://www.google.com";
    value6 = 1;
    value7 = "http://www.google.com";
    value8 = 4SY8KcTSGeKuKs7s;
}' of class '__NSCFDictionary'.  Note that dictionaries and arrays in property lists must also contain only property values.`

Как видите, все объекты в dict являются значениями списка свойств, а все его ключи — NSString*. Каких мелочей мне не хватает, чтобы выполнить это? Или я должен отказаться и использовать writeToFile или аналогичный?


person Thomson Comer    schedule 24.02.2012    source источник
comment
Вы просто печатаете ключи. Что насчет ценностей?   -  person Lily Ballard    schedule 24.02.2012
comment
Вот и все, одно из моих значений прокралось как NSNull (JSON null, созданный JSONKit), и это почти определенно причина. Я еще не работал над решением.   -  person Thomson Comer    schedule 24.02.2012


Ответы (3)


Реквизит Кевину, который предложил распечатать значения, конечно, одно из которых имеет тип NSNull, который не является значением списка свойств. Спасибо!

Неуклюжее решение моей проблемы - перебирать ключи словаря, которые я только что создал так удобно, с помощью dictionaryWithValuesForKeys и удалять ключи типа NSNull. вздох

NSMutableDictionary* dict = [NSMutableDictionary dictionaryWithDictionary:[self dictionaryWithValuesForKeys:keys]];
for( id key in [dict allKeys] )
{
    if( [[dict valueForKey:key] isKindOfClass:[NSNull class]] )
    {
        // doesn't work - values that are entered will never be removed from NSUserDefaults
        //[dict removeObjectForKey:key];
        [dict setObject@"" forKey:key];
    }
}
person Thomson Comer    schedule 24.02.2012
comment
Кстати, я ненавижу тебя JSONKit и твой !@#%@#! NSNullс - person Thomson Comer; 24.02.2012
comment
Я определил, что [dict removeObjectForKey:key]; на самом деле недостаточное решение для моей проблемы, потому что теперь отсутствующие значения никогда не будут перезаписаны в NSUserDefaults. Вместо этого я теперь сохраняю пустую строку при обнаружении NSNull, как это отражено в ответе. - person Thomson Comer; 25.02.2012

Я обычно архивирую и разархивирую словари при сохранении их по умолчанию. Таким образом, вам не нужно вручную проверять значения NSNull.

Просто добавьте следующие два метода в свой код. Возможно в категории.

- (BOOL)archive:(NSDictionary *)dict withKey:(NSString *)key {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *data = nil;
    if (dict) {
        data = [NSKeyedArchiver archivedDataWithRootObject:dict];
    }
    [defaults setObject:data forKey:key];
    return [defaults synchronize];
}

- (NSDictionary *)unarchiveForKey:(NSString *)key {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *data = [defaults objectForKey:key];
    NSDictionary *userDict = nil;
    if (data) {
        userDict = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    }
    return userDict;
}

Затем вы можете заархивировать любой подобный словарь (при условии, что метод доступен в классе):

NSDictionary *dict = ...;
[self archive:dict withKey:@"a key of your choice"];

и получить его позже снова следующим образом:

NSDictionary *dict = [self unarchiveForKey:@"a key of your choice"];
person leviathan    schedule 25.06.2013

Если вам нужно хранить;

  • данные из пользовательского объекта,
  • или массив пользовательских объектов

вы можете использовать методы NSKeyedArchiver. Вы можете проверить ответ левиафана для этого метода.


Однако, если вы пытаетесь сохранить;

  • словарь, содержащий либо NSString, либо NSNumber (например, словарь, преобразованный из ответа службы JSON),
  • или массив такого словаря

вам не нужно использовать NSKeyedArchiver. Вы можете использовать пользовательские настройки по умолчанию.


В моем случае, когда я извлекаю словарь из пользовательских значений по умолчанию, он возвращает ноль, поэтому я подумал, что NSUserDefaults не желает сохранять мой словарь.

Тем не менее, он был сохранен, но я использовал неправильный метод получения, чтобы получить его из пользовательских значений по умолчанию;

[[NSUserDefaults standardUserDefaults] stringForKey:<my_key>]

Пожалуйста, убедитесь, что вы использовали либо;

[[NSUserDefaults standardUserDefaults] dictionaryForKey:<my_key>]

or;

[[NSUserDefaults standardUserDefaults] objectForKey:<my_key>]

перед проверкой любой другой возможной причины.

person Berk    schedule 16.03.2015
comment
Почему я должен использовать objectForKey по умолчанию? Я думаю, что явное приведение типов не является хорошей практикой, и его следует избегать. Также мой комментарий исправляет ту же проблему, не так ли? Голосуя против моего комментария (если вы это сделали), вы лишаете людей возможности добраться до рабочего примера. @Яро - person Berk; 11.09.2015
comment
1: это не ответ на вопрос, потому что преобразование словаря в строку возможно и устраняет проблему в этом случае, НО это не решает проблему наличия «нулевых» объектов внутри словаря, что приводит к тому, что Dictianary не может быть сохранено в NSUserDefaults.. 2: я думаю, что неявный - лучший выбор дизайна. Моя причина состоит в том, чтобы избежать этих (много раз неожиданных) ошибок, которые у вас были. Я не хочу оставлять безделушки с проверкой типов для NSUserDefaults. Лучше написать методы Архиватора, если вы не хотите быть таким явным. - person Yaro; 13.09.2015
comment
1: Если основными данными является словарь, преобразование его в строку бесполезно, не нужно, плохое программирование. Я не говорю, что нужна конверсия. [NSUserDefaults DictionaryForKey:MY_KEY] вообще не преобразует данные. Но если вы сохраните основные данные с типом и попытаетесь получить их с другим типом, он вернет ноль, так как никогда не сохранялся. Основная проблема не в том, что данные не сохраняются, а в том, что это вызывает точно такой же симптом. 2: Избегание опечаток - основная проблема IDE и самого программиста. Дизайн программирования должен включать в себя другие аспекты, такие как стоимость модульного тестирования, связность, четкие зависимости и т. д. - person Berk; 14.09.2015
comment
так вы загрузили этот словарь как строку или нет? Как я проверял, это возможно. - person Yaro; 24.09.2015
comment
Я попробовал еще раз, и это невозможно. Вы можете увидеть мой тестовый код и журнал ниже. Журнал: 28.09.2015 13:45:18.159 Тестовое приложение[20772:1787919] stringForKey: (null) 28.09.2015 13:45:18.159 Тестовое приложение[20772:1787919] DictionForKey: { obj = key; } Код: [[NSUserDefaults standardUserDefaults] setObject:@{@obj:@key} forKey:@KEY]; [[NSUserDefaults standardUserDefaults] синхронизировать]; NSLog(@stringForKey: %@, [[NSUserDefaults standardUserDefaults] stringForKey:@KEY]); NSLog(@dictionaryForKey: %@, [[NSUserDefaults standardUserDefaults] DictionaryForKey:@KEY]); - person Berk; 28.09.2015
comment
1: Итак, в своем ответе я имел в виду, что любой, кто совершает ту же ошибку, должен использовать словарьForKey. -› Это устраняет проблему. 2: Итак, данные на самом деле сохраняются, но симптом тот же, он возвращает ноль, как будто он вообще не сохранялся. -› Вот почему я думаю, что мой ответ указывает на тот же вопрос. 3: В общем, я не понимаю, почему за мой ответ проголосовали против. - person Berk; 28.09.2015