Dlaczego NSUserDefaults nie może zapisać NSMutableDictionary w iOS?

145

Chciałbym zapisać NSMutableDictionaryobiekt w formacie NSUserDefaults. Typ klucza w NSMutableDictionaryto NSString, typ wartości to NSArray, który zawiera listę obiektów, które implementują NSCoding. Na dokument NSStringi NSArrayoba są zgodne z NSCoding.

Otrzymuję ten błąd:

[NSUserDefaults setObject: forKey:]: Próba wstawienia wartości innej niż właściwość .... klasy NSCFDictionary.

BlueDolphin
źródło

Odpowiedzi:

230

Znalazłem jedną alternatywę, przed zapisaniem koduję obiekt główny ( NSArrayobiekt) za pomocą NSKeyedArchiver, który kończy się na NSData. Następnie użyj UserDefaults, zapisz plik NSData.

Kiedy potrzebuję danych, czytam NSDatai używam NSKeyedUnarchiverdo konwersji z NSDatapowrotem do obiektu.

Jest to trochę uciążliwe, ponieważ NSDataza każdym razem muszę konwertować na / z , ale po prostu działa.

Oto jeden przykład na żądanie:

Zapisać:

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

Załaduj:

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

Element w tablicy implementuje

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

Następnie w realizacji CommentItem, zapewnia dwie metody:

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

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

Czy ktoś ma lepsze rozwiązanie?

Dziękuję wszystkim.

BlueDolphin
źródło
4
Czy masz przykład kodu, którym możesz się podzielić, próbowałem to zrobić w poście (537044), ale bez radości
Anthony Main
To działało dobrze dla mnie. Próbowałem zakodować NSArray obiektów NSDictionary, w których niektóre klucze nie były łańcuchami. Po prostu użycie NSKeyedArchiver i Unarchiver na NSArray pozbyło się błędu „Próba wstawienia wartości innej niż własność”.
John Wright,
Kiedy muszę zwolnić załadowany obiekt? Nie wywołano alokacji, więc zakładam, że jest to autorelasing?
Marc
7
możemy potrzebować dodać [defaults synchronize] do zapisania
thanhbinh84
Przedstawione tutaj rozwiązanie może przez chwilę działać, ale gdy zdecydujesz się usunąć lub zmienić klasę, utkniesz w bałaganie. Lepszym rozwiązaniem jest zapisanie stanu obiektu za pomocą numerów NS, ciągów znaków itp. W słowniku, a następnie zapisanie tego. Gotowy na przyszłość.
Tom Andersen
105

Jeśli zapisujesz obiekt w ustawieniach domyślnych użytkownika, wszystkie obiekty rekurencyjnie, aż do końca, muszą być obiektami listy właściwości. Zgodność z NSCoding nic tutaj nie znaczy - nie zakoduje NSUserDefaultsich automatycznie NSData, musisz to zrobić sam. Jeśli twoja „lista obiektów, które implementują NSCoding” oznacza obiekty, które nie są obiektami listy właściwości, to będziesz musiał coś z nimi zrobić przed zapisaniem ich do ustawień domyślnych użytkownika.

FYI zajęcia listy właściwości są NSDictionary, NSArray, NSString, NSDate, NSData, i NSNumber. Możesz pisać zmienne podklasy (takie jak NSMutableDictionary) do preferencji użytkownika, ale odczytywane obiekty zawsze będą niezmienne.

Tom Harrington
źródło
61
Powinienem również wspomnieć, że NSDictionary jest obiektem listy właściwości tylko wtedy, gdy klucze są NSStrings. Ogólnie można użyć innych obiektów jako kluczy słownika, ale tam, gdzie są wymagane listy właściwości, klucze muszą być łańcuchami.
Tom Harrington,
Natknąłem się na ten sam problem, gdy chciałem przechowywać NSURL jako obiekt w NSDictionary i przechowywać go w NSUserDefaults. Musiałem zmienić to na NSString, ale zadziałało.
NicTesla
1
Wspaniały! W moim przypadku próbowałem użyć NSNumber jako klucza NSDictionary w ustawieniach domyślnych użytkownika. Muszę to zmienić na NSString.
Joey
1
Takie opóźnione zachowanie prowadzi do tego, że niedoświadczeni programiści piszą cały model danych jako słowniki zamiast tworzyć odpowiednie obiekty danych? Jak dziwnie trudne może być dla Apple'a pisanie podstawowych klas, które wykorzystują fakt, że obj-c ma introspekcję i zajmują się boksowaniem i rozpakowywaniem za nas?
ArtOfWarfare
14

Czy wszystkie twoje klucze są w słowniku NSString? Myślę, że muszą być, aby zapisać słownik na liście właściwości.

Sophie Alpert
źródło
1
klucze to NSString. ale wartością jest NSArray, którego elementem jest dostosowana klasa implementująca NSCoding. Wygląda na to, że rozwiązanie nie działa, ponieważ UserDefaults obsługuje tylko 6 typów klas, nie biorąc pod uwagę NSCoding.
BlueDolphin
8

Najprostsza odpowiedź:

NSDictionaryjest tylko obiektem plist , jeśli klucze są NSStrings . Więc przechowuj „Klucz” jak NSStringw stringWithFormat.


Rozwiązanie :

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

Korzyści :

  1. Doda String-Value .
  2. Doda pustą wartość, gdy twoja wartość zmiennej to NULL.
Bhavin
źródło
2
Miałem podobny problem: moje klucze były numerami NS, które najwyraźniej nie są dozwolone w kluczach słowników plist.
Mark Pauley,
5

Czy zastanawiałeś się nad wdrożeniem NSCodingprotokołu? Umożliwi to kodowanie i dekodowanie na telefonie iPhone za pomocą dwóch prostych metod zaimplementowanych w programie NSCoding. Najpierw musisz dodać NSCodingdo swojej klasy.

Oto przykład:

To jest w pliku .h

@interface GameContent : NSObject <NSCoding>

Następnie będziesz musiał zaimplementować dwie metody protokołu 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"];
    }

Aby uzyskać więcej informacji, zapoznaj się z dokumentacją dotyczącą NSCoder. Przydało się to naprawdę w moich projektach, w których muszę zapisać stan aplikacji na iPhonie, jeśli aplikacja jest zamknięta i przywrócić ją do stanu, w którym jest ponownie włączona.

Kluczem jest dodanie protokołu do interfejsu, a następnie zaimplementowanie dwóch metod, które są jego częścią NSCoding.

Mam nadzieję, że to pomoże!

Niels Hansen
źródło
0

Nie ma lepszego rozwiązania. Inną opcją byłoby po prostu zapisanie zakodowanego obiektu na dysku - ale to robi to samo. Oba kończą z NSData, który jest dekodowany, gdy chcesz go z powrotem.

Dan Keen
źródło