Por que o NSUserDefaults falhou ao salvar o NSMutableDictionary no iOS?

145

Eu gostaria de salvar um NSMutableDictionaryobjeto NSUserDefaults. O tipo de chave NSMutableDictionaryé NSString, o tipo de valor é NSArray, que contém uma lista de objetos que implementa NSCoding. Por documento NSStringe NSArrayambos estão em conformidade NSCoding.

Estou recebendo este erro:

[NSUserDefaults setObject: forKey:]: tenta inserir um valor não de propriedade .... da classe NSCFDictionary.

BlueDolphin
fonte

Respostas:

230

Descobri uma alternativa: antes de salvar, codifico o objeto raiz ( NSArrayobjeto) usando NSKeyedArchiver, que termina com NSData. Em seguida, use UserDefaults e salve o NSData.

Quando preciso dos dados, leio o NSDatae uso NSKeyedUnarchiverpara converter NSDatanovamente no objeto.

É um pouco complicado, porque eu preciso converter de / para NSDatasempre, mas funciona.

Aqui está um exemplo por solicitação:

Salve :

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

Carga:

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

O elemento na matriz implementa

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

Em seguida, na implementação de CommentItem, fornece dois métodos:

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

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

Alguém tem melhor solução?

Obrigado a todos.

BlueDolphin
fonte
4
Você tem um exemplo de código que você pode compartilhar, eu tenho tentado fazer isso no post (537044), mas sem alegria
Anthony Principal
Isso funcionou muito bem para mim. Eu estava tentando codificar um NSArray de objetos NSDictionary onde algumas das chaves não eram seqüências de caracteres. O simples uso do NSKeyedArchiver e Unarchiver no NSArray se livrou do erro "Tentativa de inserir valor não-propriedade".
31711 John Wright
Quando preciso liberar o objeto carregado? Nenhuma alocação foi chamada, então eu diria que é autorelased?
Marc
7
Podemos precisar adicionar [defaults sincronizar] para salvar
thanhbinh84
A solução postada aqui pode funcionar por um tempo, mas quando você decidir remover ou alterar a classe, ficará preso em uma bagunça. Uma solução melhor é fazer com que seu objeto escreva seu estado usando NSNumbers, Strings etc. em um dicionário e armazene-o. Prova do futuro.
Tom Andersen
105

Se você estiver salvando um objeto nos padrões do usuário, todos os objetos, recursivamente, até o fim, devem ser objetos da lista de propriedades. Conformidade com NSCoding não significa nada aqui - NSUserDefaultsnão os codificará automaticamente NSData, você deve fazer isso sozinho. Se a sua "lista de objetos que implementaNSCoding " significa objetos que não são objetos da lista de propriedades, será necessário fazer alguma coisa com eles antes de salvar nos padrões do usuário.

FYI as classes de lista propriedade são NSDictionary, NSArray, NSString, NSDate, NSData, e NSNumber. Você pode escrever subclasses mutáveis ​​(como NSMutableDictionary) nas preferências do usuário, mas os objetos que você ler sempre serão imutáveis.

Tom Harrington
fonte
61
Também devo mencionar que um NSDictionary é apenas um objeto da lista de propriedades se as chaves forem NSStrings. Em geral, você pode usar outros objetos como chaves de dicionário, mas onde as listas de propriedades são necessárias, as chaves devem ser cadeias de caracteres.
Tom Harrington
Me deparei com o mesmo problema, quando eu queria armazenar NSURL como um objeto em um NSDictionary e armazená-lo nos NSUserDefaults. Eu tive que transformá-lo em um NSString, do que funcionou.
NicTesla
1
Ótimo! No meu caso, tentei usar o NSNumber como uma chave do NSDictionary nos padrões do usuário. Eu tenho que mudar para um NSString.
Joey
1
Esse comportamento retardado é o que leva os codificadores inexperientes a escreverem todo o seu modelo de dados como dicionários, em vez de criarem objetos de dados adequados? Quão difícil pode ser para a Apple escrever classes básicas que utilizam o fato de que a obj-c tem introspecção e cuida de boxe e unboxing para nós?
ArtOfWarfare
14

Todas as suas chaves estão no dicionário NSString? Eu acho que eles precisam estar salvos o dicionário em uma lista de propriedades.

Sophie Alpert
fonte
1
as chaves são NSString. mas o valor é NSArray, cujo elemento é uma classe personalizada que implementa NSCoding. Parece que a solução não está funcionando porque UserDefaults suporta apenas 6 tipos de classe, não considerando NSCoding.
BlueDolphin
8

Resposta mais simples:

NSDictionaryé apenas um objeto plist , se as chaves forem NSStrings . Portanto, guarde a "Chave" como NSStringem stringWithFormat.


Solução:

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

Benefícios:

  1. Ele adicionará String-Value .
  2. Ele adicionará valor vazio quando seu valor de variável for NULL.
Bhavin
fonte
2
Eu tive um problema semelhante: minhas chaves eram NSNumbers, que aparentemente não são permitidas nas chaves dos dicionários plist.
Mark Pauley
5

Você já pensou em implementar o NSCoding Protocolo? Isso permitirá que você codifique e decodifique no iPhone com dois métodos simples que são implementados com o NSCoding. Primeiro você precisaria adicionar oNSCoding à sua classe.

Aqui está um exemplo:

Este está no arquivo .h

@interface GameContent : NSObject <NSCoding>

Então você precisará implementar dois métodos do Protocolo 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"];
    }

Confira a documentação do NSCoder para obter mais informações. Isso foi realmente útil para meus projetos, nos quais eu preciso salvar o estado do aplicativo no iPhone se o aplicativo estiver fechado e restaurá-lo ao seu estado quando ele voltar a funcionar.

A chave é adicionar o protocolo à interface e, em seguida, implementar os dois métodos que fazem parte do NSCoding .

Eu espero que isso ajude!

Niels Hansen
fonte
0

Não há solução melhor. Outra opção seria salvar apenas o objeto codificado no disco - mas isso está fazendo a mesma coisa. Ambos acabam com o NSData que é decodificado quando você o quer de volta.

Dan Keen
fonte