พยายามตั้งค่าวัตถุที่ไม่ใช่รายการทรัพย์สินเป็น NSUserDefaults


197

ฉันคิดว่าฉันรู้ว่าสิ่งใดที่ทำให้เกิดข้อผิดพลาดนี้ แต่ฉันไม่สามารถเข้าใจได้ว่าฉันทำอะไรผิด

นี่คือข้อความแสดงข้อผิดพลาดทั้งหมดที่ฉันได้รับ:

พยายามตั้งค่าวัตถุที่ไม่ใช่รายการทรัพย์สิน (
   "<BC_Person: 0x8f3c140>"
) เป็นค่า NSUserDefaults สำหรับคีย์ personDataArray

ฉันมีPersonชั้นเรียนที่ฉันคิดว่าสอดคล้องกับNSCodingโปรโตคอลที่ฉันมีทั้งสองวิธีในชั้นเรียนส่วนตัวของฉัน:

- (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 บางNSStringในBC_PersonClassการตั้งค่าและฉันมีชั้นเรียนที่ผมคิดว่าจะจัดการการเข้ารหัสคุณสมบัติในของฉันDataSave BC_PersonClassนี่คือรหัสที่ฉันใช้จากDataSaveชั้นเรียน:

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

ฉันหวังว่านี่เป็นรหัสที่เพียงพอที่จะให้แนวคิดแก่คุณเกี่ยวกับสิ่งที่ฉันพยายามจะทำ อีกครั้งฉันรู้ว่าปัญหาของฉันอยู่ที่ว่าฉันเข้ารหัสคุณสมบัติของฉันในคลาส BC_Person ของฉันฉันแค่ดูเหมือนจะไม่สามารถคิดออกว่าฉันกำลังทำอะไรผิด

ขอบคุณสำหรับความช่วยเหลือ!


สงสัยว่าเราจะตรวจสอบว่ามันเป็นวัตถุรายการทรัพย์สินหรือไม่
onmyway133

that I think is conforming to the NSCoding protocolมันง่ายมากที่จะเพิ่มการทดสอบหน่วยสำหรับสิ่งนั้นและคุ้มค่าจริงๆ
rr1g0

ดีที่สุดที่จะทำคือการตรวจสอบพารามิเตอร์ของคุณฉันพบว่าฉันกำลังเพิ่มสตริงซึ่งเป็นตัวเลขดังนั้นที่ไม่ได้ใช้จึงเป็นปัญหา
Rajal

คำตอบ:


272

NSUserDefaultsรหัสที่คุณโพสต์พยายามที่จะบันทึกอาร์เรย์ที่กำหนดเองของวัตถุ คุณทำอย่างนั้นไม่ได้ การนำNSCodingวิธีการมาใช้ไม่ได้ช่วยอะไร คุณสามารถเพียงสิ่งที่ชอบเก็บNSArray, NSDictionary, NSString, NSData, NSNumberและในNSDateNSUserDefaults

คุณจะต้องแปลงวัตถุNSData(เช่นคุณมีในบางส่วนของรหัส) และร้านค้าที่อยู่ในNSData NSUserDefaultsคุณสามารถจัดเก็บNSArrayของNSDataหากคุณต้องการ

เมื่อคุณอ่านอาเรย์กลับคืนคุณจำเป็นต้องยกเลิกการเก็บถาวรอาร์เคตNSDataเพื่อเรียกคืนBC_Personวัตถุของคุณ

บางทีคุณอาจต้องการสิ่งนี้:

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

คำถามหนึ่งที่ฉันมี หากฉันต้องการเพิ่ม personEncodedObject ในอาเรย์แล้วใส่อาเรย์ลงในข้อมูลผู้ใช้ ... ฉันจะแทนที่: [archiveArray addObject: personEncodedObject]; ด้วย NSArray และเพิ่ม addObject: personEncodedObject ในอาร์เรย์นั้นแล้วบันทึกใน userData หรือไม่ ถ้าคุณทำตามสิ่งที่ฉันพูด
icekomo

ฮะ? ฉันคิดว่าคุณพิมพ์ผิดเนื่องจากคุณต้องการแทนที่บรรทัดของโค้ดด้วยโค้ดบรรทัดเดียวกัน รหัสของฉันใส่อาร์เรย์ของวัตถุที่เข้ารหัสในค่าเริ่มต้นของผู้ใช้
rmaddy

ฉันเดาว่าฉันหลงทางเพราะคิดว่าคุณสามารถใช้ NSArray กับ userDefaults เท่านั้น แต่ฉันเห็นในตัวอย่างของคุณว่าคุณกำลังใช้อาร์เรย์ NSMutable บางทีฉันก็ไม่เข้าใจบางสิ่งบางอย่าง ...
icekomo

2
NSMutableArrayNSArrayขยาย มันสมบูรณ์ดีที่จะผ่านNSMutableArrayไปยังวิธีการใด ๆ NSArrayที่คาดว่า โปรดทราบว่าอาร์เรย์ที่จัดเก็บจริงNSUserDefaultsจะไม่เปลี่ยนเมื่อคุณอ่านกลับ
rmaddy

1
สิ่งนี้มีค่าใช้จ่ายหรือไม่ ตัวอย่างเช่นหากสิ่งนี้ทำงานผ่านลูปและเติมวัตถุคลาสแบบกำหนดเองหลาย ๆ ครั้งจากนั้นเปลี่ยนเป็น NSData ก่อนเพิ่มแต่ละอันลงในอาเรย์สิ่งนี้จะมีปัญหาด้านประสิทธิภาพมากกว่าการส่งประเภทข้อมูลปกติไปยังอาเรย์หรือไม่
Rob85

66

ดูเหมือนว่าจะเป็นการสิ้นเปลืองสำหรับฉันในการวิ่งผ่านอาเรย์และเข้ารหัสวัตถุเป็น NSData ด้วยตัวคุณเอง ข้อผิดพลาดของคุณBC_Person is a non-property-list objectกำลังบอกคุณว่ากรอบงานไม่ทราบวิธีการเรียงลำดับวัตถุส่วนบุคคลของคุณ

ดังนั้นสิ่งที่จำเป็นต้องมีก็คือตรวจสอบให้แน่ใจว่าวัตถุบุคคลของคุณสอดคล้องกับ NSCoding แล้วคุณสามารถแปลงอาร์เรย์ของวัตถุที่กำหนดเองเป็น NSData และเก็บไว้เป็นค่าเริ่มต้น นี่คือสนามเด็กเล่น:

แก้ไข : การเขียนไปยังNSUserDefaultsถูกทำลายบน Xcode 7 ดังนั้นสนามเด็กเล่นจะเก็บถาวรไปยังข้อมูลและสำรองและพิมพ์ผลลัพธ์ ขั้นตอน UserDefaults รวมอยู่ในกรณีที่ได้รับการแก้ไขในภายหลัง

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

และ Voila คุณเก็บอาร์เรย์ของวัตถุที่กำหนดเองไว้ใน NSUserDefaults


คำตอบนี้มั่นคง! ฉันมีวัตถุที่สอดคล้องกับ NSCoder แต่ฉันลืมเกี่ยวกับอาร์เรย์ที่เก็บไว้ขอบคุณช่วยฉันหลายชั่วโมง!
มิคาเอลเฮลล์แมน

1
@ แดเนียลฉันเพิ่งวางรหัสของคุณในขณะที่อยู่ใน Playground xcode 7.3 และมันมีข้อผิดพลาดใน "ให้เรียกค้นข้อมูลจากคน: [บุคคล] = เรียกคืนคน ()!" -> EXC_BAD_INSTRUCTION (รหัส = EXC_I386_INVOP, รหัสย่อย = 0x0) ฉันต้องทำการปรับอะไรบ้าง ขอบคุณ
rockhammer

1
@rockhammer ดูเหมือนว่า NSUserDefaults ไม่สามารถใช้งานได้ใน Playgrounds เนื่องจาก Xcode 7 :( จะอัปเดตในไม่ช้า
Daniel Galasko

1
@Daniel ไม่มีปัญหา ฉันไปข้างหน้าและใส่รหัสของคุณลงในโครงการของฉันและมันทำงานได้อย่างมีเสน่ห์! คลาสอ็อบเจ็กต์ของฉันมี NSDate นอกเหนือจาก 3 ประเภทสตริง ฉันต้องการแทนที่ NSDate สำหรับ String ในตัวถอดรหัส func ขอบคุณ
rockhammer

สำหรับ Swift3:func encode(with aCoder: NSCoder)
Achintya Ashok

51

เพื่อบันทึก:

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

วิธีรับ:

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

สำหรับการลบ

[currentDefaults removeObjectForKey:@"yourKeyName"];

34

โซลูชัน Swift 3

คลาสยูทิลิตี้ที่เรียบง่าย

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

}

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

    }
}

วิธีการโทร

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
ดี! : D Adaptation ที่เร็วที่สุดที่ดีที่สุด +1
Gabo Cuadros Cardenas

developer.apple.com/documentation/foundation/userdefaults/ ... ... "วิธีนี้ไม่จำเป็นและไม่ควรใช้" ฮ่า ๆ ... ใครบางคนใน apple ไม่ใช่ผู้เล่นในทีม
Nerdy Bunz

12

Swift- 4 Xcode 9.1

ลองรหัสนี้

คุณไม่สามารถเก็บ mapper ใน NSUserDefault คุณสามารถเก็บ NSData, NSString, NSNumber, NSDate, NSArray หรือ 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)

8
ขอบคุณสำหรับสิ่งนี้. NSKeyedArchiver.archivedData (withRootObject :) ได้รับการคัดค้านใน iOS12 เป็นวิธีการใหม่ที่ทำให้เกิดข้อผิดพลาด อาจเป็นการอัพเดทรหัสของคุณใช่ไหม :)
Marcy

10

ก่อนอื่นคำตอบของ rmaddy (ด้านบน) ถูกต้องการใช้NSCodingไม่ได้ผล แต่คุณจำเป็นต้องใช้NSCodingในการใช้NSKeyedArchiverและสิ่งที่จึงเป็นเพียงหนึ่งในขั้นตอนอื่น ๆ ... NSDataแปลงผ่าน

ตัวอย่างวิธีการ

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

ดังนั้นคุณจึงสามารถห่อของNSCodingวัตถุในNSArrayหรือNSDictionaryหรืออะไรก็ตาม ...


6

NSUserDefaultsฉันมีปัญหานี้พยายามประหยัดพจนานุกรมไป ปรากฎว่ามันจะไม่บันทึกเพราะมันมีNSNullค่า ดังนั้นฉันเพิ่งคัดลอกพจนานุกรมไปไว้ในพจนานุกรมที่ไม่แน่นอนและลบค่า Null แล้วบันทึกลงNSUserDefaults

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

ในกรณีนี้ฉันรู้ว่าคีย์ใดอาจเป็นNSNullค่า


5

รวดเร็วด้วย@propertyWrapper

บันทึกCodableวัตถุไปที่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
}

ตัวอย่างรุ่นผู้ใช้ยืนยันรหัส

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

วิธีใช้งาน

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

//GetValue
print(GlobalSettings.user)

นี่คือคำตอบที่ดีที่สุด Stacker ใช้คำตอบนี้เป็นมาตราส่วนอย่างแท้จริง
Stefan Vasiljevic

4

สวิฟท์ 5: Codableโปรโตคอลสามารถนำมาใช้แทนการNSKeyedArchiever

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

Pref struct เป็นที่กำหนดเองห่อหุ้มรอบวัตถุมาตรฐาน UserDefaults

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

ดังนั้นคุณสามารถใช้วิธีนี้:

Pref.user?.name = "John"

if let user = Pref.user {...

1
if let data = UserDefaults.standard.data(forKey: keyUser) {และ Btw แคสต์จากUserถึงUserเป็นเพียงไร้สาระreturn try JSONDecoder().decode(User.self, from: data)
Leo Dabus

3

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

วัตถุเริ่มต้นจะต้องเป็นรายการคุณสมบัตินั่นคืออินสแตนซ์ของ (หรือสำหรับคอลเลกชันรวมกันของอินสแตนซ์): NSData, NSString, NSNumber, NSDate, NSArray หรือ NSDictionary

หากคุณต้องการเก็บวัตถุประเภทอื่น ๆ โดยทั่วไปคุณควรเก็บถาวรเพื่อสร้างอินสแตนซ์ของ NSData สำหรับรายละเอียดเพิ่มเติมโปรดดูคู่มือการตั้งค่าและการตั้งค่า


3

Swift 5วิธีง่ายมาก

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

นี่เป็นเพียงโครงสร้าง codable เท่านั้น
Kishore Kumar

ฉันได้รับ: 'archivedData (withRootObject :)' ถูกคัดค้านใน macOS 10.14: ใช้ + archivedDataWithRootObject: RequireSecureCoding: error: แทนที่จะ
multitudes
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.