Cố gắng đặt đối tượng không thuộc tính danh sách làm NSUserDefaults


196

Tôi nghĩ rằng tôi biết những gì đã gây ra lỗi này, nhưng tôi dường như không thể tìm ra những gì tôi đã làm sai.

Đây là thông báo lỗi đầy đủ tôi đang nhận được:

Cố gắng đặt đối tượng không thuộc tính danh sách (
   "<BC_Person: 0x8f3c140>"
) làm giá trị NSUserDefaults cho key personDataArray

Tôi có một Personlớp mà tôi nghĩ là phù hợp với NSCodinggiao thức, nơi tôi có cả hai phương thức này trong lớp người của mình:

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

Tại một số điểm trong ứng dụng, phần NSStringtrong BC_PersonClassđược đặt và tôi có một DataSavelớp mà tôi nghĩ là đang xử lý mã hóa các thuộc tính trong của tôi BC_PersonClass. Đây là mã tôi đang sử dụng từ DataSavelớp:

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

Tôi hy vọng đây là mã đủ để cung cấp cho bạn một ý tưởng o những gì tôi đang cố gắng làm. Một lần nữa tôi biết vấn đề của mình nằm ở cách tôi mã hóa các thuộc tính của mình trong lớp BC_Person, dường như tôi không thể hiểu được mặc dù tôi đang làm gì sai.

Cảm ơn đã giúp đỡ!


Tự hỏi làm thế nào chúng ta có thể kiểm tra xem nó có phải là đối tượng danh sách tài sản hay không
onmyway133

that I think is conforming to the NSCoding protocolthật dễ dàng để thêm thử nghiệm đơn vị cho điều đó, và thực sự đáng giá.
rr1g0

Cách tốt nhất là kiểm tra các tham số của bạn. Tôi phát hiện ra rằng tôi đã thêm một chuỗi là một số. Vì vậy, điều đó không được sử dụng, do đó là vấn đề.
Rajal

Câu trả lời:


272

Mã bạn đã đăng cố lưu một mảng các đối tượng tùy chỉnh vào NSUserDefaults. Bạn không thể làm điều đó. Thực hiện các NSCodingphương pháp không giúp đỡ. Bạn chỉ có thể lưu trữ những thứ như NSArray, NSDictionary, NSString, NSData, NSNumber, và NSDatetrong NSUserDefaults.

Bạn cần chuyển đổi đối tượng thành NSData(giống như bạn có trong một số mã) và lưu trữ nó NSDatatrong NSUserDefaults. Bạn thậm chí có thể lưu trữ một NSArraysố NSDatanếu bạn cần.

Khi bạn đọc lại mảng, bạn cần hủy lưu trữ NSDatađể lấy lại các BC_Personđối tượng của bạn .

Có lẽ bạn muốn điều này:

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

Một câu hỏi tôi có. Nếu tôi muốn thêm personEncodingObject vào một mảng và sau đó đưa mảng vào Dữ liệu người dùng ... tôi có thể chỉ cần thay thế: [archiveArray addObject: personEncodingObject]; với NSArray và thêm addObject: personEncodingObject vào mảng đó và sau đó lưu nó trong userData? Nếu bạn làm theo những gì tôi đang nói.
icekomo

Huh? Tôi nghĩ bạn tạo một lỗi đánh máy vì bạn muốn thay thế một dòng mã bằng cùng một dòng mã. Mã của tôi không đặt mảng các đối tượng được mã hóa trong mặc định của người dùng.
rmaddy

Tôi đoán tôi đã mất vì tôi nghĩ bạn chỉ có thể sử dụng NSArray với userDefaults, nhưng tôi thấy trong ví dụ của bạn, bạn đang sử dụng mảng NSMutable. Có lẽ tôi chỉ không hiểu điều gì đó ...
icekomo

2
NSMutableArraykéo dài NSArray. Hoàn toàn ổn khi truyền một NSMutableArrayphương thức mà bạn mong đợi NSArray. Hãy nhớ rằng mảng thực sự được lưu trữ NSUserDefaultssẽ không thay đổi khi bạn đọc lại.
rmaddy

1
Điều này có chi phí gì trong hiệu suất? ví dụ: nếu điều này đang chạy qua một vòng lặp và đưa vào một đối tượng lớp tùy chỉnh nhiều lần rồi thay đổi nó thành NSData trước khi thêm từng đối tượng vào một mảng, thì điều này có vấn đề hiệu năng nào lớn hơn việc chỉ truyền các loại dữ liệu thông thường vào mảng không?
Rob85

66

Nó có vẻ khá lãng phí đối với tôi khi chạy qua mảng và mã hóa các đối tượng vào NSData. Lỗi của bạn BC_Person is a non-property-list objectđang nói với bạn rằng khung không biết cách tuần tự hóa đối tượng người của bạn.

Vì vậy, tất cả những gì cần thiết là để đảm bảo rằng đối tượng người của bạn tuân thủ NSCoding, sau đó bạn có thể chuyển đổi mảng đối tượng tùy chỉnh của mình thành NSData và lưu trữ nó thành mặc định. Đây là một sân chơi:

Chỉnh sửa : Viết thành NSUserDefaultsbị hỏng trên Xcode 7, do đó sân chơi sẽ lưu trữ dữ liệu và quay lại và in một đầu ra. Bước UserDefaults được bao gồm trong trường hợp được cố định tại một điểm sau đó

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

Và Voila, bạn đã lưu trữ một loạt các đối tượng tùy chỉnh vào NSUserDefaults


Câu trả lời này là vững chắc! Tôi đã có các đối tượng của mình phù hợp với NSCoder, nhưng tôi quên mất mảng mà chúng được lưu trữ. Cảm ơn, đã tiết kiệm cho tôi nhiều giờ!
Mikael Hellman

1
@Daniel, tôi vừa dán mã của bạn vào sân chơi xcode 7.3 và nó đang báo lỗi về "let Lấy raP People: [Person] = lấyPPP ()!" -> EXC_BAD_INXD (mã = EXC_I386_INVOP, mã con = 0x0). Tôi cần điều chỉnh gì? Cảm ơn
rockhammer

1
@rockhammer có vẻ như NSUserDefaults không hoạt động trong Playgrounds vì Xcode 7 :( sẽ cập nhật sớm
Daniel Galasko

1
@Daniel, không vấn đề gì. Tôi đã tiếp tục và chèn mã của bạn vào dự án của tôi và nó hoạt động như một cơ duyên! Lớp đối tượng của tôi có NSDate ngoài 3 loại Chuỗi. Tôi chỉ cần thay thế NSDate cho String trong giải mã func. Cảm ơn
rockhammer

Dành cho Swift3:func encode(with aCoder: NSCoder)
Achintya Ashok

51

Để tiết kiệm:

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

Để có được:

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

Để xóa

[currentDefaults removeObjectForKey:@"yourKeyName"];

34

Giải pháp Swift 3

Lớp tiện ích đơn giản

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

}

Lớp mẫu

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

    }
}

Cách gọi

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
Đẹp! : D Thích ứng nhanh nhất +1
Gabo Cuadros Cardenas

developer.apple.com/documentation/foundation/userdefaults/ trên ... "phương pháp này là không cần thiết và không nên được sử dụng." LOL ... ai đó ở apple không phải là người chơi trong đội.
Nerdy Bunz

12

Swift- 4 Xcode 9.1

thử mã này

bạn không thể lưu trữ trình ánh xạ trong NSUserDefault, bạn chỉ có thể lưu trữ NSData, NSString, NSNumber, NSDate, NSArray hoặc NSDipedia.

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
Cảm ơn vì điều này. NSKeyedArchiver.archivingData (withRootObject :) đã bị phản đối trong iOS12 đối với một phương thức mới gây ra lỗi. Có lẽ một bản cập nhật cho mã của bạn? :)
Marcy

10

Trước hết, câu trả lời của rmaddy (ở trên) là đúng: thực NSCodinghiện không giúp được gì. Tuy nhiên, bạn cần triển khai NSCodingđể sử dụng NSKeyedArchivervà tất cả những thứ đó, vì vậy đây chỉ là một bước nữa ... chuyển đổi qua NSData.

Phương pháp ví dụ

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

Vì vậy, bạn có thể bọc của bạn NSCodingđối tượng trong một NSArrayhoặc NSDictionaryhoặc bất cứ điều gì ...


6

Tôi đã có vấn đề này khi cố gắng lưu một từ điển NSUserDefaults. Hóa ra nó sẽ không lưu vì nó chứa NSNullcác giá trị. Vì vậy, tôi chỉ sao chép từ điển vào một từ điển có thể thay đổi loại bỏ các null sau đó được lưu vàoNSUserDefaults

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

Trong trường hợp này, tôi biết khóa nào có thể là NSNullgiá trị.


4

Swift 5: Giao thức Codable có thể được sử dụng thay vì NSKeyedArchiever .

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

Cấu trúc Pref là trình bao bọc tùy chỉnh xung quanh đối tượng tiêu chuẩn 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)
            }
        }
    }
}

Vì vậy, bạn có thể sử dụng nó theo cách này:

Pref.user?.name = "John"

if let user = Pref.user {...

1
if let data = UserDefaults.standard.data(forKey: keyUser) {và đúc Btw từ Userđến Userlà vô nghĩareturn try JSONDecoder().decode(User.self, from: data)
Leo Dabus

4

Swift với@propertyWrapper

Lưu Codableđối tượng vàoUserDefault

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

Ví dụ Mô hình người dùng xác nhận Codable

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

Làm thế nào để sử dụng nó

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

//GetValue
print(GlobalSettings.user)

Đây là TRẢ LỜI HIỆN ĐẠI tốt nhất. Stacker, sử dụng câu trả lời này là có khả năng mở rộng thực sự.
Stefan Vasiljevic

3

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

Một đối tượng mặc định phải là một danh sách thuộc tính, đó là một thể hiện của (hoặc cho các bộ sưu tập, một sự kết hợp của các thể hiện): NSData, NSString, NSNumber, NSDate, NSArray hoặc NSDipedia.

Nếu bạn muốn lưu trữ bất kỳ loại đối tượng nào khác, bạn thường nên lưu trữ nó để tạo một thể hiện của NSData. Để biết thêm chi tiết, xem Tùy chọn và Hướng dẫn lập trình cài đặt.


3

Swift 5 cách rất dễ dàng

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

cái này chỉ dành cho cấu trúc mã hóa
Kishore Kumar

Tôi nhận được: 'archuredData (withRootObject :)' không được dùng nữa trong macOS 10.14: Sử dụng + archuredDataWithRootObject: notifySecureCoding: error: thay vào đó
multitudes
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.