NSUserDefaults'da bir NSMutableArray'de özel nesneleri saklama


101

Yakın zamanda iPhone uygulamamın arama sonuçlarını NSUserDefaults koleksiyonunda saklamaya çalışıyorum. Bunu kullanıcı kayıt bilgilerini başarıyla kaydetmek için de kullanıyorum, ancak bazı nedenlerden dolayı NSMutableArray of custom Location sınıflarımı saklamaya çalışmak her zaman boş geliyor.

NSMutableArray'i bu gönderiden itibaren bir NSData öğesine dönüştürmeyi denedim, ancak aynı sonucu alıyorum ( iPhone'da NSUserDefaults kullanarak bir tamsayı dizisi kaydetmek mümkün mü? )

Denediğim kod örnekleri:

Kayıt etmek:

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

veya

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

veya

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

Yük:

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

veya

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

veya

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

Aşağıdaki tavsiyeye uyduktan sonra, nesnemde NSCoder'ı da uyguladım (NSString'in aşırı kullanımını geçici olarak göz ardı edin):

#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"];

}

Yanıtlar:


177

Bir diziye özel nesneler yüklemek için, diziyi almak için kullandım:

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];
}

Kullanıcı varsayılanlarından döndürülen verilerin sıfır olmadığını kontrol etmelisiniz, çünkü sıfırdan arşivden çıkarmanın çökmeye neden olduğuna inanıyorum.

Arşivleme, aşağıdaki kodu kullanarak basittir:

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

F3lix'in işaret ettiği gibi, özel nesnenizi NSCoding protokolüne uygun hale getirmeniz gerekir. Aşağıdaki gibi yöntemler eklemek işe yaramalı:

- (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;
}

3
İnitWithCoder: yönteminizin sonunda bir "dönüş öz" eksiktiniz. Bu, arşivden çıkarma işleminin gerçekleşmesine izin veriyor gibi görünüyor.
Brad Larson

2
Diziler nsuserdefaults için fazla büyüyebileceğinden dizileri nsuserdefaults olarak depolamamalısınız. Bunun yerine + (BOOL) archiveRootObject: (id) rootObject toFile: (NSString *) yolu kullanılarak bir dosyada saklanmalıdır. Bu cevap neden bu kadar çok oylandı?
mskw

1
@mskw - NSUserDefaults'un büyük dizilerin depolanması için kullanılmaması gerektiği konusunda haklısınız (bunun yerine Core Data veya ham SQLite kullanılmalıdır), ancak küçük kodlanmış nesneler dizilerini orada saklamak tamamen iyidir. Her durumda, soru bunun nasıl yapılacağını sormaktı, bu yukarıdaki kodun sağladığı şeydir. Oylara gelince, tahmininiz benimki kadar iyi. Son dört yılda birçok insan bunu yapmak istemiş olmalı.
Brad Larson

2
@Goodsum - Hayır, bu gayet iyi çalışıyor. Deneyin, gerçekten değiştirilebilir olan bir NSMutableArray döndürür. Yalnızca yeni diziyi, bu öğe dizisini ekleyerek başlatır.
Brad Larson

1
@AlbertRenshaw - andSyncYukarıdaki koda eklediniz nereden geldi? Bu, NSUserDefaults için gerçek bir yöntem imzası değildir. Ayrıca, synchronizeeskisi kadar kullanışlı değil: twitter.com/Catfish_Man/status/647274106056904704
Brad Larson

13

İnitWithCoder yönteminizde bir hata aldığınızı düşünüyorum, en azından sağlanan kodda 'self' nesnesini döndürmüyorsunuz.

- (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


}

kullanırım

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

ve

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];
}

ve benim için mükemmel çalışıyor.

Saygılarımla,

Stefan


3
Stephan, sen hayat kurtarıcısın, buraya bak; Bu satır aklımı kurtardı "if (self = [super init])" stackoverflow.com/questions/1933285/…
fyasar

ERROR_BAD_ACCESS aldım ama beni kurtardın. Özelliği sadece muhafaza ile kullanmaktı ve self.variable'ı eklemek sorunu çözdü.
David

0

Kodunuzdaki sorunun sonuç dizisini kaydetmemesi gibi görünüyor. Verileri yüklemeyi kullanmayı deneyin

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

Bu, anahtar tarafından belirtilen diziyi döndürür.


0

"NSUserDefaults neden NSMutableDictionary'u iPhone SDK'ya kaydedemedi?" Konusuna bakın (NSUserDefaults, NSMutableDictionary'u iPhone SDK'ya neden kaydedemedi? )


Özel nesneleri serileştirmek (de) istiyorsanız, verileri (de) serileştirmek için (NSCoding protokolü) işlevler sağlamanız gerekir. Başvurduğunuz çözüm int dizisi ile çalışır çünkü dizi bir nesne değil, bitişik bir bellek öbeğidir.


Sanırım hakkınız NSCoding'e bir göz atacağım (önerilen bir bağlantınız yoksa), nesnelerin kendileri NSStrings üzerinde oluşturulmuştur, bu yüzden sanırım sadece kendi serileştiricimi yazabilir ve hepsini bir dizi dizisine koyabilirim
Anthony Main

Ok ben NSCode yukarıda bkz uygulamış, ama yine de hiçbir sevinç NSArchiver kullanarak
Anthony Main

0

Bu gibi şeyleri varsayılanlar db'de saklamaya karşı tavsiye ederim.

SQLite'ı almak ve kullanmak oldukça kolaydır. Ekran kayıtlarımdan birinde ( http://pragprog.com/screencasts/v-bdiphone ) yazdığım basit bir paketleyici hakkında bir bölüm var ( SC'yi satın almadan kodu alabilirsiniz).

Uygulama verilerini uygulama alanında depolamak çok daha temizdir.

Tüm bunlar, bu verileri varsayılanlar db'ye koymak hala mantıklıysa, o zaman f3lix'in yayınladığı gönderiye bakın.


1
Öneriniz için teşekkürler, ancak yalnızca birkaç dizeli bir nesne için (örneğinizde gördüğüm gibi) SQL sorguları yazmaya başlamak zorunda kalmak istemiyorum
Anthony Main
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.