Berusaha menetapkan objek non-properti-daftar sebagai NSUserDefaults

196

Saya pikir saya tahu apa yang menyebabkan kesalahan ini, tetapi sepertinya saya tidak tahu apa yang saya lakukan salah.

Inilah pesan kesalahan lengkap yang saya dapatkan:

Mencoba menetapkan objek daftar non-properti (
   "<BC_Person: 0x8f3c140>"
) sebagai nilai NSUserDefaults untuk key personDataArray kunci

Saya memiliki Personkelas yang menurut saya sesuai dengan NSCodingprotokol, di mana saya memiliki kedua metode ini di kelas pribadi saya:

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.personsName forKey:@"BCPersonsName"];
    [coder encodeObject:self.personsBills forKey:@"BCPersonsBillsArray"];
}

- (id)initWithCoder:(NSCoder *)coder {
    self = [super init];
    if (self) {
        self.personsName = [coder decodeObjectForKey:@"BCPersonsName"];
        self.personsBills = [coder decodeObjectForKey:@"BCPersonsBillsArray"];
    }
    return self;
}

Di beberapa titik di aplikasi, NSStringdi BC_PersonClassdiatur, dan saya memiliki DataSavekelas yang saya pikir menangani pengkodean properti di saya BC_PersonClass. Berikut adalah kode yang saya gunakan dari DataSavekelas:

- (void)savePersonArrayData:(BC_Person *)personObject
{
   // NSLog(@"name of the person %@", personObject.personsName);

    [mutableDataArray addObject:personObject];

    // set the temp array to the mutableData array
    tempMuteArray = [NSMutableArray arrayWithArray:mutableDataArray];

    // save the person object as nsData
    NSData *personEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:personObject];

    // first add the person object to the mutable array
    [tempMuteArray addObject:personEncodedObject];

    // NSLog(@"Objects in the array %lu", (unsigned long)mutableDataArray.count);

    // now we set that data array to the mutable array for saving
    dataArray = [[NSArray alloc] initWithArray:mutableDataArray];
    //dataArray = [NSArray arrayWithArray:mutableDataArray];

    // save the object to NS User Defaults
    NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
    [userData setObject:dataArray forKey:@"personDataArray"];
    [userData synchronize];
}

Saya harap ini kode yang cukup untuk memberi Anda ide tentang apa yang saya coba lakukan. Sekali lagi saya tahu masalah saya terletak pada bagaimana saya mengkodekan properti saya di kelas BC_Person saya, saya sepertinya tidak bisa mencari tahu apa yang saya lakukan meskipun salah.

Terima kasih untuk bantuannya!

icekomo
sumber
Bertanya-tanya bagaimana kita dapat memeriksa apakah itu objek daftar properti atau tidak
onmyway133
that I think is conforming to the NSCoding protocolsangat mudah untuk menambahkan pengujian unit untuk itu, dan sangat berharga.
rr1g0
Yang terbaik untuk dilakukan adalah memeriksa parameter Anda. Saya menemukan bahwa saya menambahkan string yang merupakan angka. Jadi itu tidak digunakan, maka itu masalahnya.
Rajal

Jawaban:

272

Kode yang Anda posting mencoba untuk menyimpan array objek khusus NSUserDefaults. Kamu tidak bisa melakukan itu Menerapkan NSCodingmetode tidak membantu. Anda hanya dapat menyimpan hal-hal seperti NSArray, NSDictionary, NSString, NSData, NSNumber, dan NSDatedi NSUserDefaults.

Anda perlu mengonversi objek menjadi NSData(seperti yang Anda miliki di beberapa kode) dan menyimpannya NSDatadi NSUserDefaults. Anda bahkan dapat menyimpan salah NSArraysatu NSDatajika perlu.

Ketika Anda membaca kembali array Anda harus membatalkan pengarsipan NSDatauntuk mendapatkan kembali BC_Personobjek Anda .

Mungkin Anda menginginkan ini:

- (void)savePersonArrayData:(BC_Person *)personObject {
    [mutableDataArray addObject:personObject];

    NSMutableArray *archiveArray = [NSMutableArray arrayWithCapacity:mutableDataArray.count];
    for (BC_Person *personObject in mutableDataArray) { 
        NSData *personEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:personObject];
        [archiveArray addObject:personEncodedObject];
    }

    NSUserDefaults *userData = [NSUserDefaults standardUserDefaults];
    [userData setObject:archiveArray forKey:@"personDataArray"];
}
rmaddy
sumber
Satu pertanyaan yang saya miliki. Jika saya ingin menambahkan personEncodedObject ke dalam array dan kemudian memasukkan array ke dalam data pengguna ... bisakah saya mengganti: [archiveArray addObject: personEncodedObject]; dengan NSArray dan tambahkan addObject: personEncodedObject ke dalam array itu dan kemudian simpan di userData? Jika Anda mengikuti apa yang saya katakan.
icekomo
Hah? Saya pikir Anda membuat kesalahan ketik karena Anda ingin mengganti satu baris kode dengan baris kode yang sama. Kode saya tidak meletakkan array objek yang disandikan di default pengguna.
rmaddy
Saya kira saya tersesat karena saya pikir Anda hanya bisa menggunakan NSArray dengan userDefaults, tapi saya lihat dalam contoh Anda Anda menggunakan array NSMutable. Mungkin saya hanya tidak mengerti sesuatu ...
icekomo
2
NSMutableArraymeluas NSArray. Tidak apa-apa untuk meneruskan NSMutableArrayke metode apa pun yang mengharapkan NSArray. Perlu diingat bahwa array yang disimpan sebenarnya NSUserDefaultstidak akan berubah ketika Anda membacanya kembali.
rmaddy
1
Apakah ini ada biaya dalam kinerja? misalnya jika ini dijalankan melalui loop dan mengisi objek kelas kustom berkali-kali kemudian mengubahnya ke NSData sebelum menambahkan masing-masing ke array, apakah ini memiliki masalah kinerja yang lebih besar daripada hanya meneruskan tipe data normal ke array?
Rob85
66

Sepertinya agak boros bagi saya untuk menjalankan melalui array dan mengkodekan objek ke dalam NSData sendiri. Kesalahan andaBC_Person is a non-property-list object memberi tahu Anda bahwa kerangka kerja tidak tahu cara membuat serial objek objek Anda.

Jadi semua yang diperlukan adalah untuk memastikan bahwa objek orang Anda sesuai dengan NSCoding maka Anda dapat dengan mudah mengkonversi array objek kustom Anda ke NSData dan menyimpannya ke default. Inilah taman bermain:

Sunting : Menulis ke NSUserDefaultsrusak pada Xcode 7 sehingga taman bermain akan mengarsipkan ke data dan kembali dan mencetak output. Langkah UserDefaults disertakan jika diperbaiki pada titik berikutnya

//: Playground - noun: a place where people can play

import Foundation

class Person: NSObject, NSCoding {
    let surname: String
    let firstname: String

    required init(firstname:String, surname:String) {
        self.firstname = firstname
        self.surname = surname
        super.init()
    }

    //MARK: - NSCoding -
    required init(coder aDecoder: NSCoder) {
        surname = aDecoder.decodeObjectForKey("surname") as! String
        firstname = aDecoder.decodeObjectForKey("firstname") as! String
    }

    func encodeWithCoder(aCoder: NSCoder) {
        aCoder.encodeObject(firstname, forKey: "firstname")
        aCoder.encodeObject(surname, forKey: "surname")
    }
}

//: ### Now lets define a function to convert our array to NSData

func archivePeople(people:[Person]) -> NSData {
    let archivedObject = NSKeyedArchiver.archivedDataWithRootObject(people as NSArray)
    return archivedObject
}

//: ### Create some people

let people = [Person(firstname: "johnny", surname:"appleseed"),Person(firstname: "peter", surname: "mill")]

//: ### Archive our people to NSData

let peopleData = archivePeople(people)

if let unarchivedPeople = NSKeyedUnarchiver.unarchiveObjectWithData(peopleData) as? [Person] {
    for person in unarchivedPeople {
        print("\(person.firstname), you have been unarchived")
    }
} else {
    print("Failed to unarchive people")
}

//: ### Lets try use NSUserDefaults
let UserDefaultsPeopleKey = "peoplekey"
func savePeople(people:[Person]) {
    let archivedObject = archivePeople(people)
    let defaults = NSUserDefaults.standardUserDefaults()
    defaults.setObject(archivedObject, forKey: UserDefaultsPeopleKey)
    defaults.synchronize()
}

func retrievePeople() -> [Person]? {
    if let unarchivedObject = NSUserDefaults.standardUserDefaults().objectForKey(UserDefaultsPeopleKey) as? NSData {
        return NSKeyedUnarchiver.unarchiveObjectWithData(unarchivedObject) as? [Person]
    }
    return nil
}

if let retrievedPeople = retrievePeople() {
    for person in retrievedPeople {
        print("\(person.firstname), you have been unarchived")
    }
} else {
    print("Writing to UserDefaults is still broken in playgrounds")
}

Dan Voila, Anda telah menyimpan array objek kustom ke NSUserDefaults

Daniel Galasko
sumber
Jawaban ini solid! Saya memiliki objek yang sesuai dengan NSCoder, tetapi saya lupa tentang array tempat penyimpanannya. Terima kasih, menyelamatkan saya berjam-jam!
Mikael Hellman
1
@Daniel, saya baru saja menempelkan kode Anda ke taman bermain xcode 7.3 dan itu memberikan kesalahan pada "biarkan RetaluPeople: [Orang] = RetvePeople ()!" -> EXC_BAD_INSTRUCTION (kode = EXC_I386_INVOP, subcode = 0x0). Penyesuaian apa yang harus saya lakukan? Terima kasih
rockhammer
1
@rockhammer sepertinya NSUserDefaults tidak berfungsi di Playgrounds karena Xcode 7 :( akan segera diperbarui
Daniel Galasko
1
@Aniel, tidak masalah. Saya pergi ke depan dan memasukkan kode Anda ke proyek saya dan itu berfungsi seperti pesona! Kelas objek saya memiliki NSDate selain 3 tipe String. Saya hanya perlu mengganti NSDate untuk String di func decode. Terima kasih
rockhammer
Untuk Swift3:func encode(with aCoder: NSCoder)
Achintya Ashok
51

Untuk menyimpan:

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *data = [NSKeyedArchiver archivedDataWithRootObject:yourObject];
[currentDefaults setObject:data forKey:@"yourKeyName"];

Mendapatkan:

NSData *data = [currentDefaults objectForKey:@"yourKeyName"];
yourObjectType * token = [NSKeyedUnarchiver unarchiveObjectWithData:data];

Untuk Hapus

[currentDefaults removeObjectForKey:@"yourKeyName"];
jose920405
sumber
34

Solusi Swift 3

Kelas utilitas sederhana

class ArchiveUtil {

    private static let PeopleKey = "PeopleKey"

    private static func archivePeople(people : [Human]) -> NSData {

        return NSKeyedArchiver.archivedData(withRootObject: people as NSArray) as NSData
    }

    static func loadPeople() -> [Human]? {

        if let unarchivedObject = UserDefaults.standard.object(forKey: PeopleKey) as? Data {

            return NSKeyedUnarchiver.unarchiveObject(with: unarchivedObject as Data) as? [Human]
        }

        return nil
    }

    static func savePeople(people : [Human]?) {

        let archivedObject = archivePeople(people: people!)
        UserDefaults.standard.set(archivedObject, forKey: PeopleKey)
        UserDefaults.standard.synchronize()
    }

}

Kelas Model

class Human: NSObject, NSCoding {

    var name:String?
    var age:Int?

    required init(n:String, a:Int) {

        name = n
        age = a
    }


    required init(coder aDecoder: NSCoder) {

        name = aDecoder.decodeObject(forKey: "name") as? String
        age = aDecoder.decodeInteger(forKey: "age")
    }


    public func encode(with aCoder: NSCoder) {

        aCoder.encode(name, forKey: "name")
        aCoder.encode(age, forKey: "age")

    }
}

Bagaimana cara menelepon

var people = [Human]()

people.append(Human(n: "Sazzad", a: 21))
people.append(Human(n: "Hissain", a: 22))
people.append(Human(n: "Khan", a: 23))

ArchiveUtil.savePeople(people: people)

let others = ArchiveUtil.loadPeople()

for human in others! {

    print("name = \(human.name!), age = \(human.age!)")
}
Sazzad Hissain Khan
sumber
1
Bagus! : D adaptasi cepat terbaik +1
Gabo Cuadros Cardenas
developer.apple.com/documentation/foundation/userdefaults/ ... ... "metode ini tidak perlu dan tidak boleh digunakan." LOL ... seseorang di apel bukan pemain tim.
Nerdy Bunz
12

Swift- 4 Xcode 9.1

coba kode ini

Anda tidak dapat menyimpan mapper di NSUserDefault, Anda hanya dapat menyimpan NSData, NSString, NSNumber, NSDate, NSArray, atau NSDictionary.

let myData = NSKeyedArchiver.archivedData(withRootObject: myJson)
UserDefaults.standard.set(myData, forKey: "userJson")

let recovedUserJsonData = UserDefaults.standard.object(forKey: "userJson")
let recovedUserJson = NSKeyedUnarchiver.unarchiveObject(with: recovedUserJsonData)
Vishal Vaghasiya
sumber
8
Terima kasih untuk ini. NSKeyedArchiver.archivedData (withRootObject :) telah ditinggalkan di iOS12 ke metode baru yang melempar kesalahan. Mungkin pembaruan untuk kode Anda? :)
Marcy
10

Pertama, jawaban rmaddy (di atas) benar: implementasi NSCodingtidak membantu. Namun, Anda perlu menerapkan NSCodinguntuk menggunakan NSKeyedArchiverdan semua itu, jadi itu hanya satu langkah lagi ... mengkonversi melalui NSData.

Metode contoh

- (NSUserDefaults *) defaults {
    return [NSUserDefaults standardUserDefaults];
}

- (void) persistObj:(id)value forKey:(NSString *)key {
    [self.defaults setObject:value  forKey:key];
    [self.defaults synchronize];
}

- (void) persistObjAsData:(id)encodableObject forKey:(NSString *)key {
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:encodableObject];
    [self persistObj:data forKey:key];
}    

- (id) objectFromDataWithKey:(NSString*)key {
    NSData *data = [self.defaults objectForKey:key];
    return [NSKeyedUnarchiver unarchiveObjectWithData:data];
}

Jadi, Anda dapat membungkus NSCodingbenda Anda di dalam NSArrayatau NSDictionary...

Dan Rosenstark
sumber
6

Saya mengalami masalah saat mencoba menyimpan kamus ini ke NSUserDefaults. Ternyata itu tidak akan disimpan karena mengandung NSNullnilai. Jadi saya hanya menyalin kamus ke dalam kamus yang bisa diubah menghapus nulls kemudian disimpanNSUserDefaults

NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithDictionary:dictionary_trying_to_save];
[dictionary removeObjectForKey:@"NullKey"];
[[NSUserDefaults standardUserDefaults] setObject:dictionary forKey:@"key"];

Dalam hal ini saya tahu kunci mana yang mungkin menjadi NSNullnilai.

simple_code
sumber
4

Swift 5: Protokol Codable dapat digunakan sebagai pengganti NSKeyedArchiever .

struct User: Codable {
    let id: String
    let mail: String
    let fullName: String
}

The Pref struct kebiasaan pembungkus di UserDefaults objek standar.

struct Pref {
    static let keyUser = "Pref.User"
    static var user: User? {
        get {
            if let data = UserDefaults.standard.object(forKey: keyUser) as? Data {
                do {
                    return try JSONDecoder().decode(User.self, from: data)
                } catch {
                    print("Error while decoding user data")
                }
            }
            return nil
        }
        set {
            if let newValue = newValue {
                do {
                    let data = try JSONEncoder().encode(newValue)
                    UserDefaults.standard.set(data, forKey: keyUser)
                } catch {
                    print("Error while encoding user data")
                }
            } else {
                UserDefaults.standard.removeObject(forKey: keyUser)
            }
        }
    }
}

Jadi Anda bisa menggunakannya dengan cara ini:

Pref.user?.name = "John"

if let user = Pref.user {...
Prcela
sumber
1
if let data = UserDefaults.standard.data(forKey: keyUser) {dan Btw casting dari Userke Usertidak ada gunanya hanyareturn try JSONDecoder().decode(User.self, from: data)
Leo Dabus
4

Cepat dengan@propertyWrapper

Simpan Codableobjek keUserDefault

@propertyWrapper
    struct UserDefault<T: Codable> {
        let key: String
        let defaultValue: T

        init(_ key: String, defaultValue: T) {
            self.key = key
            self.defaultValue = defaultValue
        }

        var wrappedValue: T {
            get {

                if let data = UserDefaults.standard.object(forKey: key) as? Data,
                    let user = try? JSONDecoder().decode(T.self, from: data) {
                    return user

                }

                return  defaultValue
            }
            set {
                if let encoded = try? JSONEncoder().encode(newValue) {
                    UserDefaults.standard.set(encoded, forKey: key)
                }
            }
        }
    }




enum GlobalSettings {

    @UserDefault("user", defaultValue: User(name:"",pass:"")) static var user: User
}

Contoh Model pengguna mengonfirmasi Codable

struct User:Codable {
    let name:String
    let pass:String
}

Bagaimana cara menggunakannya

//Set value 
 GlobalSettings.user = User(name: "Ahmed", pass: "Ahmed")

//GetValue
print(GlobalSettings.user)
dimo hamdy
sumber
Ini adalah JAWABAN MODERN terbaik. Stacker, gunakan jawaban ini benar-benar dapat diukur.
Stefan Vasiljevic
3

https://developer.apple.com/reference/foundation/userdefaults

Objek default harus berupa daftar properti — yaitu turunan dari (atau untuk koleksi, kombinasi dari instance): NSData, NSString, NSNumber, NSDate, NSArray, atau NSDictionary.

Jika Anda ingin menyimpan objek jenis apa pun, Anda harus mengarsipkannya untuk membuat instance NSData. Untuk detail lebih lanjut, lihat Panduan Pemrograman Preferensi dan Pengaturan.

Isaak Osipovich Dunayevsky
sumber
3

Swift 5 Cara Sangat Mudah

//MARK:- First you need to encoded your arr or what ever object you want to save in UserDefaults
//in my case i want to save Picture (NMutableArray) in the User Defaults in
//in this array some objects are UIImage & Strings

//first i have to encoded the NMutableArray 
let encodedData = NSKeyedArchiver.archivedData(withRootObject: yourArrayName)
//MARK:- Array save in UserDefaults
defaults.set(encodedData, forKey: "YourKeyName")

//MARK:- When you want to retreive data from UserDefaults
let decoded  = defaults.object(forKey: "YourKeyName") as! Data
yourArrayName = NSKeyedUnarchiver.unarchiveObject(with: decoded) as! NSMutableArray

//MARK: Enjoy this arrry "yourArrayName"
Shakeel Ahmed
sumber
ini hanya untuk struct kode
Kishore Kumar
Saya mendapatkan: 'archivedData (withRootObject :)' sudah tidak digunakan lagi di macOS 10.14: Gunakan + archivedDataWithRootObject: membutuhkanSecureCoding: error: Sebaliknya
multitas