Przechowywanie obiektów niestandardowych w NSMutableArray w NSUserDefaults

101

Ostatnio próbowałem przechowywać wyniki wyszukiwania mojej aplikacji na iPhone'a w kolekcji NSUserDefaults. Używam tego również do pomyślnego zapisania informacji o rejestracji użytkownika, ale z jakiegoś powodu próba zapisania mojej NSMutableArray niestandardowych klas lokalizacji zawsze jest pusta.

Próbowałem przekonwertować NSMutableArray na element NSData od tego postu, ale otrzymuję ten sam wynik (Czy można zapisać tablicę liczb całkowitych za pomocą NSUserDefaults na iPhonie? )

Próbki kodu, które wypróbowałem, to:

Zapisać:

[prefs setObject:results forKey:@"lastResults"];
[prefs synchronize];

lub

NSData *data = [NSData dataWithBytes:&results length:sizeof(results)];
[prefs setObject:data forKey:@"lastResults"];

lub

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];

Załaduj:

lastResults = (NSMutableArray *)[prefs objectForKey:@"lastResults"];

lub

NSData *data = [prefs objectForKey:@"lastResults"];
memcpy(&lastResults, data.bytes, data.length);  

lub

NSData *data = [prefs objectForKey:@"lastResults"];
lastResults = [NSKeyedUnarchiver unarchiveObjectWithData:data];

Po poniższych poradach zaimplementowałem również NSCoder w moim obiekcie (zignoruj ​​nadużywanie NSString jego tymczasowego):

#import "Location.h"


@implementation Location

@synthesize locationId;
@synthesize companyName;
@synthesize addressLine1;
@synthesize addressLine2;
@synthesize city;
@synthesize postcode;
@synthesize telephoneNumber;
@synthesize description;
@synthesize rating;
@synthesize priceGuide;
@synthesize latitude;
@synthesize longitude;
@synthesize userLatitude;
@synthesize userLongitude;
@synthesize searchType;
@synthesize searchId;
@synthesize distance;
@synthesize applicationProviderId;
@synthesize contentProviderId;

- (id) initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.locationId = [coder decodeObjectForKey:@"locationId"];
        self.companyName = [coder decodeObjectForKey:@"companyName"];
        self.addressLine1 = [coder decodeObjectForKey:@"addressLine1"];
        self.addressLine2 = [coder decodeObjectForKey:@"addressLine2"];
        self.city = [coder decodeObjectForKey:@"city"];
        self.postcode = [coder decodeObjectForKey:@"postcode"];
        self.telephoneNumber = [coder decodeObjectForKey:@"telephoneNumber"];
        self.description = [coder decodeObjectForKey:@"description"];
        self.rating = [coder decodeObjectForKey:@"rating"];
        self.priceGuide = [coder decodeObjectForKey:@"priceGuide"];
        self.latitude = [coder decodeObjectForKey:@"latitude"];
        self.longitude = [coder decodeObjectForKey:@"longitude"];
        self.userLatitude = [coder decodeObjectForKey:@"userLatitude"];
        self.userLongitude = [coder decodeObjectForKey:@"userLongitude"];
        self.searchType = [coder decodeObjectForKey:@"searchType"];
        self.searchId = [coder decodeObjectForKey:@"searchId"];
        self.distance = [coder decodeObjectForKey:@"distance"];
        self.applicationProviderId = [coder decodeObjectForKey:@"applicationProviderId"];
        self.contentProviderId = [coder decodeObjectForKey:@"contentProviderId"];
    }
}

- (void) encodeWithCoder: (NSCoder *)coder
{
    [coder encodeObject:locationId forKey:@"locationId"];
    [coder encodeObject:companyName forKey:@"companyName"];
    [coder encodeObject:addressLine1 forKey:@"addressLine1"];
    [coder encodeObject:addressLine2 forKey:@"addressLine2"];
    [coder encodeObject:city forKey:@"city"];
    [coder encodeObject:postcode forKey:@"postcode"];
    [coder encodeObject:telephoneNumber forKey:@"telephoneNumber"];
    [coder encodeObject:description forKey:@"description"];
    [coder encodeObject:rating forKey:@"rating"];
    [coder encodeObject:priceGuide forKey:@"priceGuide"];
    [coder encodeObject:latitude forKey:@"latitude"];
    [coder encodeObject:longitude forKey:@"longitude"];
    [coder encodeObject:userLatitude forKey:@"userLatitude"];
    [coder encodeObject:userLongitude forKey:@"userLongitude"];
    [coder encodeObject:searchType forKey:@"searchType"];
    [coder encodeObject:searchId forKey:@"searchId"];
    [coder encodeObject:distance forKey:@"distance"];
    [coder encodeObject:applicationProviderId forKey:@"applicationProviderId"];
    [coder encodeObject:contentProviderId forKey:@"contentProviderId"];

}
Anthony Main
źródło

Odpowiedzi:

177

Aby załadować niestandardowe obiekty do tablicy, skorzystałem z tego do pobrania tablicy:

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"savedArray"];
if (dataRepresentingSavedArray != nil)
{
    NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
    if (oldSavedArray != nil)
        objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
    else
        objectArray = [[NSMutableArray alloc] init];
}

Powinieneś sprawdzić, czy dane zwrócone z wartości domyślnych użytkownika nie są zerowe, ponieważ uważam, że przywrócenie z archiwum z nil powoduje awarię.

Archiwizacja jest prosta, przy użyciu następującego kodu:

[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:objectArray] forKey:@"savedArray"];

Jak zauważył f3lix, musisz dostosować swój obiekt niestandardowy do protokołu NSCoding. Dodanie metod takich jak poniższe powinno załatwić sprawę:

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

- (id)initWithCoder:(NSCoder *)coder;
{
    self = [super init];
    if (self != nil)
    {
        label = [[coder decodeObjectForKey:@"label"] retain];
        numberID = [[coder decodeIntegerForKey:@"numberID"] retain];
    }   
    return self;
}
Brad Larson
źródło
3
Brakowało "return self" na końcu metody initWithCoder :. Dodanie tego wydaje się pozwalać na cofnięcie archiwizacji.
Brad Larson
2
Nie należy przechowywać tablic w nsuserdefaults, ponieważ mogą one być zbyt duże dla nsuserdefaults. Zamiast tego powinien być zapisany w pliku przy użyciu ścieżki + (BOOL) archiveRootObject: (id) rootObject toFile: (NSString *) ścieżka. Dlaczego ta odpowiedź jest tak wysoko oceniana?
mskw
1
@mskw - Masz rację, że NSUserDefaults nie powinny być używane do przechowywania dużych tablic (zamiast tego należy używać danych podstawowych lub surowego SQLite), ale idealnie jest przechowywać tam małe tablice zakodowanych obiektów. W każdym razie pytanie dotyczyło tego, jak to zrobić, co zapewnia powyższy kod. Jeśli chodzi o głosy, twoje przypuszczenia są równie dobre jak moje. Wiele osób musiało chcieć to zrobić w ciągu ostatnich czterech lat.
Brad Larson
2
@Goodsum - Nie, to działa dobrze. Spróbuj, a zwróci NSMutableArray, który jest rzeczywiście zmienny. Po prostu rozpoczyna nową tablicę, dodając do niej tę tablicę elementów.
Brad Larson
1
@AlbertRenshaw - Skąd się wziął ten, andSyncktóry dodałeś do powyższego kodu? To nie jest rzeczywista sygnatura metody dla NSUserDefaults. Poza tym synchronizenie jest tak użyteczny jak kiedyś: twitter.com/Catfish_Man/status/647274106056904704
Brad Larson
13

Myślę, że wystąpił błąd w metodzie initWithCoder, przynajmniej w dostarczonym kodzie nie zwraca się obiektu „self”.

- (id) initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.locationId = [coder decodeObjectForKey:@"locationId"];
        self.companyName = [coder decodeObjectForKey:@"companyName"];
        self.addressLine1 = [coder decodeObjectForKey:@"addressLine1"];
        self.addressLine2 = [coder decodeObjectForKey:@"addressLine2"];
        self.city = [coder decodeObjectForKey:@"city"];
        self.postcode = [coder decodeObjectForKey:@"postcode"];
        self.telephoneNumber = [coder decodeObjectForKey:@"telephoneNumber"];
        self.description = [coder decodeObjectForKey:@"description"];
        self.rating = [coder decodeObjectForKey:@"rating"];
        self.priceGuide = [coder decodeObjectForKey:@"priceGuide"];
        self.latitude = [coder decodeObjectForKey:@"latitude"];
        self.longitude = [coder decodeObjectForKey:@"longitude"];
        self.userLatitude = [coder decodeObjectForKey:@"userLatitude"];
        self.userLongitude = [coder decodeObjectForKey:@"userLongitude"];
        self.searchType = [coder decodeObjectForKey:@"searchType"];
        self.searchId = [coder decodeObjectForKey:@"searchId"];
        self.distance = [coder decodeObjectForKey:@"distance"];
        self.applicationProviderId = [coder decodeObjectForKey:@"applicationProviderId"];
        self.contentProviderId = [coder decodeObjectForKey:@"contentProviderId"];
    }

    return self; // this is missing in the example above


}

używam

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];

i

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"lastResults"];
if (dataRepresentingSavedArray != nil)
{
        NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
        if (oldSavedArray != nil)
                objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
        else
                objectArray = [[NSMutableArray alloc] init];
}

i działa idealnie dla mnie.

Z pozdrowieniami,

Stefan


źródło
3
Stephan, jesteś ratownikiem życia, spójrz tutaj; Ten wiersz uratował mi umysł „if (self = [super init])” stackoverflow.com/questions/1933285/…
fyasar
Mam ERROR_BAD_ACCESS, ale mnie uratowałeś. Używał własności tylko z zachowaniem i dodając zmienną self.
David,
0

Wydaje mi się, że problem z twoim kodem nie polega na zapisaniu tablicy wyników. Jego ładowanie danych spróbuj użyć

lastResults = [prefs arrayForKey:@"lastResults"];

To zwróci tablicę określoną przez klucz.

Gcoop
źródło
0

Zobacz „Dlaczego NSUserDefaults nie mógł zapisać NSMutableDictionary w iPhone SDK?” ( Dlaczego NSUserDefaults nie zapisał NSMutableDictionary w iPhone SDK? )


Jeśli chcesz (de) serializować obiekty niestandardowe, musisz dostarczyć funkcje do (de) serializacji danych (protokół NSCoding). Rozwiązanie, do którego się odwołujesz, działa z tablicą int, ponieważ tablica nie jest obiektem, ale ciągłym fragmentem pamięci.

f3lix
źródło
Myślę, że masz prawo Rzucę okiem na NSCoding (chyba że masz zalecany linky), same obiekty składają się na NSStrings, to wszystko, więc przypuszczam, że mógłbym po prostu napisać własny serializator i umieścić je wszystkie w tablicy ciągów
Anthony Main
Ok, więc zaimplementowałem NSCode, patrz wyżej, ale nadal nie ma radości z używania NSArchiver
Anthony Main
0

Odradzałbym przechowywanie takich rzeczy w domyślnej bazie danych.

SQLite jest dość łatwy do pobrania i użycia. Mam odcinek w jednym z moich screencastów ( http://pragprog.com/screencasts/v-bdiphone ) o prostym opakowaniu, które napisałem (możesz pobrać kod bez kupowania SC).

Przechowywanie danych aplikacji w przestrzeni aplikacji jest znacznie czystsze.

Wszystko to powiedziawszy, jeśli nadal ma sens umieszczanie tych danych w domyślnej bazie danych, zobacz post opublikowany przez f3lix.

Bill Dudney
źródło
1
Dzięki za sugestię, ale tak naprawdę nie chcę zaczynać pisania zapytań SQL (jak widzę w twoim przykładzie) tylko dla obiektu z kilkoma ciągami znaków
Anthony Main