Menyimpan objek khusus dalam NSMutableArray di NSUserDefaults

101

Saya baru-baru ini mencoba menyimpan hasil pencarian aplikasi iPhone saya di koleksi NSUserDefaults. Saya juga menggunakan ini untuk menyimpan info pendaftaran pengguna dengan sukses, tetapi untuk beberapa alasan mencoba menyimpan NSMutableArray kelas Lokasi khusus saya selalu kembali kosong.

Saya mencoba mengubah NSMutableArray ke elemen NSData pada posting ini tetapi saya mendapatkan hasil yang sama ( Mungkin untuk menyimpan array integer menggunakan NSUserDefaults di iPhone? )

Contoh kode yang sudah saya coba adalah:

Menyimpan:

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

atau

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

atau

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

Beban:

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

atau

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

atau

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

Setelah mengikuti saran di bawah ini, saya juga telah menerapkan NSCoder di objek saya (abaikan penggunaan NSString yang berlebihan untuk sementara):

#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
sumber

Jawaban:

177

Untuk memuat objek kustom dalam array, inilah yang saya gunakan untuk mengambil array:

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

Anda harus memeriksa bahwa data yang dikembalikan dari default pengguna tidak nihil, karena saya yakin menghapus pengarsipan dari nil menyebabkan crash.

Pengarsipan sederhana, menggunakan kode berikut:

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

Seperti yang ditunjukkan f3lix, Anda perlu membuat objek khusus Anda sesuai dengan protokol NSCoding. Menambahkan metode seperti berikut ini akan membantu:

- (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
sumber
3
Anda kehilangan "diri yang kembali" di akhir metode initWithCoder: Anda. Menambahkan yang tampaknya memungkinkan pengarsipan berlangsung.
Brad Larson
2
Anda tidak boleh menyimpan array ke dalam nsuserdefaults karena array bisa menjadi terlalu besar untuk nsuserdefaults. Ini harus disimpan ke dalam file menggunakan + (BOOL) archiveRootObject: (id) rootObject toFile: jalur (NSString *). Mengapa jawaban ini sangat disukai?
mskw
1
@mskw - Anda benar dalam NSUserDefaults itu tidak boleh digunakan untuk penyimpanan array besar (Core Data atau SQLite mentah harus digunakan untuk itu sebagai gantinya), tetapi tidak masalah untuk menyimpan array kecil dari objek yang dikodekan di sana. Bagaimanapun, pertanyaannya adalah menanyakan bagaimana melakukan ini, yang disediakan oleh kode di atas. Sejauh suara, tebakan Anda sama bagusnya dengan milik saya. Banyak orang pasti ingin melakukan ini selama empat tahun terakhir.
Brad Larson
2
@Goodsum - Tidak, itu berfungsi dengan baik. Cobalah, ini akan mengembalikan NSMutableArray yang memang bisa berubah. Ini hanya memulai larik baru dengan menambahkan larik item itu ke dalamnya.
Brad Larson
1
@AlbertRenshaw - Dari mana asal andSyncAnda menambahkan kode di atas? Itu bukan tanda tangan metode sebenarnya untuk NSUserDefaults. Juga, synchronizetidak berguna seperti dulu: twitter.com/Catfish_Man/status/647274106056904704
Brad Larson
13

Saya pikir Anda mendapatkan kesalahan dalam metode initWithCoder Anda, setidaknya dalam kode yang diberikan Anda tidak mengembalikan objek '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


}

saya menggunakan

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

dan

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

dan itu bekerja sempurna untuk saya.

Hormat kami,

Stefan


sumber
3
Stephan Anda adalah penyelamat, lihat di sini; Baris itu menyelamatkan pikiranku "if (self = [super init])" stackoverflow.com/questions/1933285/…
fyasar
Saya mendapat ERROR_BAD_ACCESS tetapi Anda menyelamatkan saya. Apakah menggunakan properti dengan hanya mempertahankan dan menambahkan variabel diri.
David
0

Saya pikir sepertinya masalah dengan kode Anda tidak menyimpan larik hasil. Ini memuat data coba gunakan

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

Ini akan mengembalikan larik yang ditentukan oleh kunci.

Gcoop
sumber
0

Lihat "Mengapa NSUserDefaults gagal menyimpan NSMutableDictionary di iPhone SDK?" ( Mengapa NSUserDefaults gagal menyimpan NSMutableDictionary di iPhone SDK? )


Jika Anda ingin (de) membuat serial objek kustom, Anda harus menyediakan fungsi untuk (de) membuat serial data (protokol NSCoding). Solusi yang Anda rujuk berfungsi dengan larik int karena larik tersebut bukan sebuah objek, melainkan potongan memori yang berdekatan.

f3lix
sumber
Saya pikir hak Anda, saya akan melihat ke NSCoding (kecuali Anda memiliki linky yang direkomendasikan), objek itu sendiri dibuat di NSStrings itu saja, jadi saya kira saya bisa menulis serialiser saya sendiri dan menempatkan semuanya ke dalam array string
Anthony Main
Ok jadi saya telah menerapkan NSCode lihat di atas, tetapi masih tidak senang menggunakan NSArchiver
Anthony Main
0

Saya akan merekomendasikan untuk mencoba menyimpan barang-barang seperti ini di default db.

SQLite cukup mudah untuk diambil dan digunakan. Saya memiliki episode di salah satu screencasts saya ( http://pragprog.com/screencasts/v-bdiphone ) tentang pembungkus sederhana yang saya tulis (Anda bisa mendapatkan kode tanpa membeli SC).

Jauh lebih bersih untuk menyimpan data aplikasi di ruang aplikasi.

Semua yang dikatakan jika masih masuk akal untuk memasukkan data ini ke db default, kemudian lihat posting f3lix yang diposting.

Bill Dudney
sumber
1
Terima kasih atas sarannya tetapi saya tidak benar-benar ingin mulai menulis kueri SQL (seperti yang saya lihat dalam contoh Anda) hanya untuk objek dengan beberapa string
Anthony Main