Özellik listesi olmayan bir nesneyi NSUserDefaults olarak ayarlama girişimi


197

Bu hataya neyin neden olduğunu bildiğimi sanıyordum, ama neyi yanlış yaptığımı anlayamıyorum.

İşte tam hata mesajı alıyorum:

Özellik listesi dışı bir nesne ayarlama girişimi (
   "<BC_Person: 0x8f3c140>"
) keyDataDataArray için bir NSUserDefaults değeri olarak

Ben kişi sınıfta bu yöntemlerin her ikisi de var Person, NSCodingprotokol uygun olduğunu düşünüyorum bir sınıf var:

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

App bir noktada, NSStringiçinde BC_PersonClassayarlanır ve ben DataSavebenim özellikleri kodlama ele olduğunu düşünüyorum bir sınıf var BC_PersonClass. İşte DataSavesınıftan kullanıyorum kodu :

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

Umarım bu size ne yapmaya çalıştığım hakkında bir fikir vermek için yeterli koddur. Yine sorunumun BC_Person sınıfımdaki mülklerimi nasıl kodladığımla ilgili olduğunu biliyorum, sadece yanlış yaptığımı anlayamıyorum.

Yardım için teşekkürler!


Özellik listesi nesnesi olup olmadığını nasıl kontrol edebiliriz
onmyway133

that I think is conforming to the NSCoding protocolbunun için birim testi eklemek çok kolay ve gerçekten buna değer.
rr1g0

En iyisi parametrelerinizi kontrol etmektir.Bir sayı olan bir dize eklediğimi öğrendim.Kullanılmaması gereken, dolayısıyla sorun oldu.
Rajal

Yanıtlar:


272

Gönderdiğiniz kod, bir dizi özel nesne kaydetmeye çalışır NSUserDefaults. Bunu yapamazsın. NSCodingYöntemleri uygulamak yardımcı olmaz. Yalnızca gibi şeyler saklayabilir NSArray, NSDictionary, NSString, NSData, NSNumber, ve NSDatede NSUserDefaults.

Sen nesneyi dönüştürmek gerekir NSDataki (kod bazı varmış gibi) ve mağaza NSDataiçinde NSUserDefaults. Hatta bir saklayabilir NSArrayait NSDataeğer gerekiyorsa.

Diziyi okuduğunuzda NSData, BC_Personnesnelerinizi geri almak için arşivden çıkarmanız gerekir .

Belki bunu istersiniz:

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

Bir sorum var. Bir diziye personEncodedObject eklemek ve sonra kullanıcı Data içine dizi koymak istedim ... ben sadece yerine: [archiveArray addObject: personEncodedObject]; NSArray ile ekleyin ve addObject: personEncodedObject öğesini bu diziye ekleyin ve bunu userData? Söylediklerimi takip edersen.
icekomo

Ha? Bir kod satırını aynı kod satırıyla değiştirmek istediğiniz için bir yazım hatası yaptığınızı düşünüyorum. Kodum, kodlanmış nesnelerin dizisini kullanıcı varsayılanlarına koyar.
rmaddy

Sanırım sadece userDefaults ile NSArray kullanabileceğinizi düşündüğüm için kayboldum, ancak örneğinizde NSMutable dizisini kullandığınızı görüyorum. Belki de sadece bir şey anlamıyorum ...
icekomo

2
NSMutableArrayuzanır NSArray. Bir beklenen NSMutableArrayherhangi bir yönteme geçmek için gayet iyi NSArray. Gerçekte depolanan dizinin NSUserDefaults, onu tekrar okuduğunuzda değişmez olacağını unutmayın.
rmaddy

1
Bunun performansta bir maliyeti var mı? örneğin, bu bir döngü boyunca çalışıyor ve birçok kez özel bir sınıf nesnesini dolduruyorsa ve her birini bir diziye eklemeden önce NSData olarak değiştiriyorsa, bu diziye normal veri türlerini iletmekten daha büyük bir performans sorunu olur mu?
Rob85

66

Dizi üzerinden çalıştırmak ve nesneleri kendiniz NSData kodlamak benim için oldukça israf görünüyor. Sizin hata BC_Person is a non-property-list objectçerçevesi sizin kişi nesne seri bilmiyor belirten edilir.

Böylece tek yapmanız gereken kişi nesnenizin NSCoding'e uygun olmasını sağlamaktır, o zaman özel nesne dizinizi NSData'ya dönüştürebilir ve bunu varsayılanlara kaydedebilirsiniz. Heres bir oyun alanı:

Düzenleme : NSUserDefaultsOyun alanı Xcode 7'de yazılır, böylece oyun alanı verileri arşivler ve geri gönderir ve bir çıktı yazdırır. UserDefaults adımı, daha sonraki bir noktada düzeltilmesi durumunda dahil edilir

//: 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")
}

Ve Voila, NSUserDefaults içine bir dizi özel nesne depoladınız


Bu cevap sağlam! Nesnelerim NSCoder ile uyumluydu, ancak saklandıkları diziyi unuttum. Teşekkürler, beni saatlerce kurtardı!
Mikael Hellman

1
@Daniel, kodunuzu oyun alanı xcode 7.3'e olduğu gibi yapıştırdım ve "let retrievedPeople: [Person] = retrievePeople ()!" -> EXC_BAD_INSTRUCTION (kod = EXC_I386_INVOP, alt kod = 0x0). Ne gibi ayarlamalar yapmam gerekiyor? Teşekkürler
rockhammer

1
@rockhammer, Xcode 7 :( yakında güncellenecek
Daniel Galasko

1
@ Daniel, sorun değil. Devam ettim ve kodumu projeme ekledim ve bir cazibe gibi çalışıyor! Nesne sınıfım 3 Dize türüne ek olarak bir NSDate'e sahip. Ben sadece kod çözme func Dize yerine NSDate yerine gerekiyordu. Teşekkürler
rockhammer

Swift3 için:func encode(with aCoder: NSCoder)
Achintya Ashok

51

Kaydetmek:

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

Almak:

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

Kaldırmak için

[currentDefaults removeObjectForKey:@"yourKeyName"];

34

Swift 3 Çözümü

Basit yardımcı sınıf

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

}

Model Sınıfı

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")

    }
}

Nasıl aranır

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!)")
}

1
Güzel! : D en iyi hızlı Adaptasyon +1
Gabo Cuadros Cardenas

developer.apple.com/documentation/foundation/userdefaults/… ... "bu yöntem gereksizdir ve kullanılmamalıdır." LOL ... elmadaki biri takım oyuncusu değil.
Nerdy Bunz

12

Swift- 4 Xcode 9.1

bu kodu dene

Eşleştiriciyi NSUserDefault'da depolayamazsınız, yalnızca NSData, NSString, NSNumber, NSDate, NSArray veya NSDictionary'yı depolayabilirsiniz.

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)

8
Bunun için teşekkür ederim. NSKeyedArchiver.archivedData (withRootObject :), iOS12'de hata veren yeni bir yönteme kaldırıldı. Belki de kodunuz için bir güncelleme? :)
Marcy

10

Öncelikle, rmaddy'nin cevabı (yukarıda) doğrudur: uygulama NSCodingyardımcı olmaz. Ancak, NSCodingkullanmak için uygulamak gerekir NSKeyedArchiverve tüm bu, bu yüzden sadece bir adım daha ... üzerinden dönüştürme NSData.

Örnek yöntemler

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

Eğer sarabilirsiniz Yani NSCodingbir in nesneleri NSArrayveya NSDictionaryya da her neyse ...


6

Bir sözlük kaydetmeye çalışırken bu sorunu yaşadım NSUserDefaults. Değer içermediği için kaydetmeyeceği ortaya çıkıyor NSNull. Bu yüzden sadece değiştirilebilen bir sözlüğe sözlük kopyaladım null'ları kaldırdım veNSUserDefaults

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

Bu durumda hangi tuşların değer olabileceğini biliyordum NSNull.


5

Swift ile@propertyWrapper

CodableNesneyi şuraya kaydet:UserDefault

@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
}

Örnek Kullanıcı modeli onaylama

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

Bu nasıl kullanılır

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

//GetValue
print(GlobalSettings.user)

Bu en iyi MODERN YANIT. İstifleyici, bu yanıtı kullanmak gerçekten ölçeklenebilir.
Stefan Vasiljevic

4

Swift 5: kodlanabilir protokol yerine kullanılabilir NSKeyedArchiever .

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

Tercihli yapı UserDefaults standart nesnenin etrafında özel sarıcı.

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)
            }
        }
    }
}

Böylece bu şekilde kullanabilirsiniz:

Pref.user?.name = "John"

if let user = Pref.user {...

1
if let data = UserDefaults.standard.data(forKey: keyUser) {ve Btw yayın yapmak Useriçin Useranlamsız adildirreturn try JSONDecoder().decode(User.self, from: data)
Leo Dabus

3

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

Varsayılan bir nesne bir özellik listesi olmalıdır - yani (veya koleksiyonlar için, örneklerin bir birleşimi): NSData, NSString, NSNumber, NSDate, NSArray veya NSDictionary.

Başka türde bir nesne saklamak istiyorsanız, genellikle bir NSData örneği oluşturmak için nesneyi arşivlemeniz gerekir. Daha fazla ayrıntı için bkz. Tercihler ve Ayarlar Programlama Kılavuzu.


3

Swift 5 Çok Kolay bir yol

//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"

Bu sadece uyumlu yapı için
Kishore Kumar

Ben olsun: 'archivedData (withRootObject :)' macOS 10.14'te kullanımdan kaldırıldı: Kullanım + archivedDataWithRootObject: requiringSecureCoding: hata: yerine
multitudes
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.