Mengapa NSUserDefaults gagal menyimpan NSMutableDictionary di iOS?

145

Saya ingin menyimpan NSMutableDictionaryobjek NSUserDefaults. Jenis kuncinya NSMutableDictionaryadalah NSString, tipe nilai adalah NSArray, yang berisi daftar objek yang mengimplementasikan NSCoding. Per dokumen, NSStringdanNSArray keduanya sesuai NSCoding.

Saya mendapatkan kesalahan ini:

[NSUserDefaults setObject: forKey:]: Berusaha menyisipkan nilai non-properti .... dari kelas NSCFDictionary.

BlueDolphin
sumber

Jawaban:

230

Saya menemukan satu alternatif, sebelum menyimpan, saya menyandikan objek root ( NSArrayobjek) menggunakan NSKeyedArchiver, yang berakhir dengan NSData. Kemudian gunakan UserDefaults simpan NSData.

Ketika saya membutuhkan data, saya membaca NSData, dan gunakan NSKeyedUnarchiveruntuk mengkonversi NSDatakembali ke objek.

Agak rumit, karena saya perlu mengkonversi ke / dari NSDatasetiap kali, tetapi hanya berfungsi.

Berikut ini satu contoh per permintaan:

Menyimpan:

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

Beban:

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

Elemen dalam array mengimplementasikan

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

Kemudian dalam implementasi CommentItem, menyediakan dua metode:

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

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

Adakah yang punya solusi yang lebih baik?

Terimakasih semuanya.

BlueDolphin
sumber
4
Apakah Anda memiliki sampel kode yang dapat Anda bagikan, saya sudah mencoba melakukan ini di pos (537044) tetapi tanpa sukacita
Anthony Main
Ini bekerja dengan baik untuk saya. Saya mencoba untuk menyandikan NSArray dari objek NSDictionary di mana beberapa kunci bukan string. Cukup menggunakan NSKeyedArchiver dan Unarchiver di NSArray menyingkirkan kesalahan "Mencoba untuk memasukkan nilai non-properti".
John Wright
Kapan saya harus melepaskan objek yang dimuat? Tidak ada alokasi yang dipanggil, jadi saya akan menganggap itu adalah autorelased?
Marc
7
kita mungkin perlu menambahkan [sinkronisasi default] untuk menyimpan
thanhbinh84
Solusi yang diposting di sini mungkin berfungsi untuk sementara waktu, tetapi ketika Anda memutuskan untuk menghapus atau mengubah kelas, Anda akan menemukan diri Anda terkunci dalam kekacauan. Solusi yang lebih baik adalah membuat objek Anda menulis statusnya menggunakan NSNumber, Strings, dll ke dalam kamus, lalu menyimpannya. Bukti masa depan.
Tom Andersen
105

Jika Anda menyimpan objek dalam default pengguna, semua objek, secara rekursif, sepenuhnya, harus objek daftar properti. Sesuai dengan NSCoding tidak berarti apa-apa di sini - NSUserDefaultstidak akan secara otomatis menyandikannya NSData, Anda harus melakukannya sendiri. Jika Anda "daftar objek yang mengimplementasikanNSCoding " berarti objek yang bukan objek daftar properti, maka Anda harus melakukan sesuatu dengannya sebelum menyimpan ke default pengguna.

FYI kelas daftar properti yang NSDictionary, NSArray, NSString, NSDate, NSData, dan NSNumber. Anda dapat menulis subclass yang dapat diubah (seperti NSMutableDictionary) ke preferensi pengguna tetapi objek yang Anda baca akan selalu tidak berubah.

Tom Harrington
sumber
61
Saya juga harus menyebutkan bahwa NSDictionary hanya objek daftar properti jika kuncinya adalah NSStrings. Secara umum Anda dapat menggunakan objek lain sebagai kunci kamus, tetapi di mana daftar properti diperlukan, kunci harus berupa string.
Tom Harrington
Saya menemukan masalah yang sama, ketika saya ingin menyimpan NSURL sebagai objek di NSDictionary dan menyimpannya di NSUserDefaults. Saya harus mengubahnya menjadi NSString, daripada berhasil.
NicTesla
1
Bagus! Dalam kasus saya, saya mencoba menggunakan NSNumber sebagai kunci NSDictionary di default pengguna. Saya harus mengubahnya menjadi NSString.
Joey
1
Perilaku terbelakang seperti itu adalah apa yang menyebabkan para pembuat kode yang tidak berpengalaman menulis seluruh model data mereka sebagai kamus alih-alih membuat objek data yang tepat? Betapa sulitnya bagi Apple untuk menulis kelas dasar yang memanfaatkan fakta bahwa obj-c memiliki introspeksi dan mengurus tinju dan unboxing untuk kita?
ArtOfWarfare
14

Apakah semua kunci Anda ada di kamus NSString? Saya pikir mereka harus untuk menyimpan kamus ke daftar properti.

Sophie Alpert
sumber
1
kuncinya adalah NSString. tetapi nilainya adalah NSArray, yang elemennya adalah kelas khusus yang mengimplementasikan NSCoding. Tampaknya solusi tidak berfungsi karena UserDefaults hanya mendukung 6 jenis kelas, tidak mempertimbangkan NSCoding.
BlueDolphin
8

Jawaban Sederhana:

NSDictionaryhanya objek plist , jika kuncinya adalah NSStrings . Jadi, Simpan "Key" sebagai NSStringdengan stringWithFormat.


Solusi:

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

Manfaat:

  1. Ini akan menambah Nilai-String .
  2. Ini akan menambah Nilai Kosong saat Nilai Variabel Anda NULL.
Bhavin
sumber
2
Saya memiliki masalah yang sama: kunci saya adalah NSNumber, yang tampaknya tidak diizinkan dalam kunci kamus plist.
Mark Pauley
5

Sudahkah Anda mempertimbangkan untuk mengimplementasikan NSCodingProtokol? Ini akan memungkinkan Anda menyandikan dan mendekode pada iPhone dengan dua metode sederhana yang diterapkan dengan NSCoding. Pertama, Anda perlu menambahkan NSCodingke Kelas Anda.

Berikut ini sebuah contoh:

Ini ada dalam file .h

@interface GameContent : NSObject <NSCoding>

Maka Anda perlu mengimplementasikan dua metode Protokol 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"];
    }

Lihat dokumentasi di NSCoder untuk informasi lebih lanjut. Itu sangat berguna untuk proyek-proyek saya di mana saya harus menyimpan status aplikasi pada iPhone jika aplikasi ditutup dan mengembalikannya kembali ke keadaan saat kembali menyala.

Kuncinya adalah menambahkan protokol ke antarmuka dan kemudian menerapkan dua metode yang merupakan bagian dari NSCoding.

Saya harap ini membantu!

Niels Hansen
sumber
0

Tidak ada solusi yang lebih baik. Pilihan lain adalah dengan hanya menyimpan objek kode ke disk - tetapi melakukan hal yang sama. Keduanya berakhir dengan NSData yang akan diterjemahkan ketika Anda menginginkannya kembali.

Dan Keen
sumber