Почему NSUserDefaults не удалось сохранить NSMutableDictionary в iOS?

145

Я хотел бы сохранить NSMutableDictionaryобъект в NSUserDefaults. Тип ключа в NSMutableDictionaryIS NSString, тип значения NSArray, которое содержит список объектов , который реализует NSCoding. За документ, NSStringи NSArrayоба соответствуют NSCoding.

Я получаю эту ошибку:

[NSUserDefaults setObject: forKey:]: Попытка вставить значение не-свойства .... класса NSCFDictionary.

BlueDolphin
источник

Ответы:

230

Я обнаружил одну альтернативу, перед сохранением кодирую корневой объект ( NSArrayобъект) с помощью NSKeyedArchiver, который заканчивается на NSData. Затем используйте UserDefaults сохранить NSData.

Когда мне нужны данные, я считываю NSDataи использую NSKeyedUnarchiverдля преобразования NSDataобратно в объект.

Это немного громоздко, потому что мне нужно конвертировать в / из NSDataкаждый раз, но это просто работает.

Вот один пример для каждого запроса:

Сохранить:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSMutableArray *arr = ... ; // set value
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:arr];
[defaults setObject:data forKey:@"theKey"];
[defaults synchronize];

Загрузить:

NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
NSData *data = [defaults objectForKey:@"theKey"];
NSArray *arr = [NSKeyedUnarchiver unarchiveObjectWithData:data];

Элемент в массиве реализует

@interface CommentItem : NSObject<NSCoding> {
    NSString *value;
}

Затем в реализации CommentItemпредусмотрены два метода:

-(void)encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeObject:value forKey:@"Value"];
}

-(id)initWithCoder:(NSCoder *)decoder
{
    self.value = [decoder decodeObjectForKey:@"Value"];
    return self;
}

У кого-нибудь есть лучшее решение?

Спасибо всем.

BlueDolphin
источник
4
У вас есть пример кода, которым вы можете поделиться, я пытался сделать это в посте (537044), но без радости
Энтони Мейн
Это хорошо сработало для меня. Я пытался закодировать NSArray объектов NSDictionary, где некоторые ключи не были строками. Простое использование NSKeyedArchiver и Unarchiver на NSArray избавило от ошибки «Попытка вставить значение не-свойства».
Джон Райт
Когда мне нужно освободить загруженный объект? Выделение не было вызвано, так что я бы предположил, что оно автоматически вызывается?
Marc
7
нам может понадобиться добавить [синхронизация по умолчанию] для сохранения
thanhbinh84
Решение, опубликованное здесь, может работать некоторое время, но когда вы решите удалить или изменить класс, вы окажетесь в беспорядке. Лучшее решение - заставить ваш объект записать свое состояние, используя NSNumbers, Strings и т. Д. В словарь, а затем сохранить его. На будущее.
Том Андерсен
105

Если вы сохраняете объект в пользовательских настройках по умолчанию, все объекты, рекурсивно, до конца, должны быть объектами списка свойств. Соответствие NSCoding ничего не значит здесь NSUserDefaults- не будет автоматически их кодировать NSData, вы должны сделать это сами. Если ваш «список объектов, который реализует NSCoding» означает объекты, которые не являются объектами списка свойств, вам придется что-то с ними сделать, прежде чем сохранять их по умолчанию.

FYI классы список недвижимости являются NSDictionary, NSArray, NSString, NSDate, NSData, и NSNumber. Вы можете записывать изменяемые подклассы (например NSMutableDictionary) в пользовательские настройки, но считанные объекты всегда будут неизменными.

Том Харрингтон
источник
61
Я должен также упомянуть, что NSDictionary является только объектом списка свойств, если ключи являются NSStrings. В общем случае вы можете использовать другие объекты в качестве ключей словаря, но там, где требуются списки свойств, ключи должны быть строками.
Том Харрингтон
Я столкнулся с той же проблемой, когда хотел сохранить NSURL как объект в NSDictionary и сохранить его в NSUserDefaults. Мне пришлось изменить его на NSString, чем это работало.
NicTesla
1
Большой! В моем случае я попытался использовать NSNumber в качестве ключа NSDictionary в пользовательских значениях по умолчанию. Я должен изменить это в NSString.
Джои
1
Такое запаздывающее поведение приводит к тому, что неопытные кодеры пишут всю свою модель данных в виде словарей вместо создания надлежащих объектов данных? Насколько сложно для Apple писать базовые классы, которые используют тот факт, что obj-c обладает самоанализом и заботятся о боксе и распаковке для нас?
ArtOfWarfare
14

Все ваши ключи в словаре NSString? Я думаю, что они должны быть для того, чтобы сохранить словарь в список свойств.

Софи Альперт
источник
1
ключи NSString. но значением является NSArray, элемент которого является настроенным классом, который реализует NSCoding. Похоже, что решение не работает, потому что UserDefaults поддерживает только 6 типов классов, не считая NSCoding.
BlueDolphin
8

Самый простой ответ:

NSDictionaryявляется только объектом plist , если ключи являются NSStrings . Итак, Храните «Ключ» как NSStringс stringWithFormat.


Решение :

NSString *key = [NSString stringWithFormat:@"%@",[dictionary valueForKey:@"Key"]];

Преимущества :

  1. Это добавит String-Value .
  2. Это добавит пустое значение, когда ваше значение переменной NULL.
Bhavin
источник
2
У меня была похожая проблема: мои ключи были NSNumbers, которые явно не разрешены в ключах словарей plist.
Марк Поли
5

Рассматривали ли вы вопрос реализации NSCodingПротокола? Это позволит вам кодировать и декодировать на iPhone двумя простыми способами, которые реализованы с помощью NSCoding. Сначала вам нужно добавить NSCodingсвой класс.

Вот пример:

Это в .h файле

@interface GameContent : NSObject <NSCoding>

Тогда вам нужно будет реализовать два метода протокола NSCoding.

    - (id) initWithCoder: (NSCoder *)coder
    {
        if (self = [super init])
        {
               [self setFoundHotSpots:[coder decodeObjectForKey:@"foundHotSpots"]];
        }
        return self;
    }

    - (void) encodeWithCoder: (NSCoder *)coder
    {
           [coder encodeObject:foundHotSpots forKey:@"foundHotSpots"];
    }

Проверьте документацию по NSCoder для получения дополнительной информации. Это очень удобно для моих проектов, где мне нужно сохранить состояние приложения на iPhone, если приложение закрыто, и вернуть его обратно в состояние, когда оно снова включено.

Ключ должен добавить протокол к интерфейсу и затем реализовать два метода, которые являются частью NSCoding.

Надеюсь, это поможет!

Нильс Хансен
источник
0

Там нет лучшего решения. Другой вариант - просто сохранить закодированный объект на диск - но это делает то же самое. Они оба заканчивают с NSData, который декодируется, когда вы хотите его вернуть.

Дэн Кин
источник