Попытка установить объект не-списка свойств как 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;
}

В какой-то момент приложения установлено значение NSStringin 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, я просто не могу понять, что, хотя я делаю неправильно.

Спасибо за помощь!

icekomo
источник
Интересно, как мы можем проверить, является ли это объектом списка свойств или нет
onmyway133
that I think is conforming to the NSCoding protocolдля этого очень просто добавить модульное тестирование, и оно того стоит.
rr1g0
Лучше всего проверить ваши параметры. Я обнаружил, что добавляю строку, которая была числом. Так что это не для использования, следовательно, была проблема.
Раджал

Ответы:

272

Размещенный вами код пытается сохранить массив пользовательских объектов NSUserDefaults. Ты не можешь этого сделать. Реализация NSCodingметодов не помогает. Вы можете хранить только такие вещи , как NSArray, NSDictionary, NSString, NSData, NSNumber, и NSDateв NSUserDefaults.

Вам нужно преобразовать объект в 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"];
}
rmaddy
источник
У меня есть один вопрос. Если бы я хотел добавить personEncodedObject в массив, а затем поместить массив в пользовательские данные ... могу ли я просто заменить: [archiveArray addObject: personEncodedObject]; с NSArray и добавить addObject: personEncodedObject в этот массив, а затем сохранить его в userData? Если вы будете следовать тому, что я говорю.
icekomo
А? Я думаю, что вы делаете опечатку, так как вы хотите заменить строку кода той же самой строкой кода. Мой код помещает массив закодированных объектов в пользовательские настройки по умолчанию.
rmaddy
Думаю, я заблудился, так как думал, что вы можете использовать NSArray только с userDefaults, но я вижу в вашем примере, что вы используете массив NSMutable. Может быть, я просто чего-то не понимаю ...
icekomo
2
NSMutableArrayрасширяется NSArray. Это прекрасно, чтобы передать NSMutableArrayлюбой метод, который ожидает NSArray. Имейте в виду, что массив, фактически сохраненный в, NSUserDefaultsбудет неизменным, когда вы будете читать его обратно.
rmaddy
1
Это стоит чего-то в производительности? например, если бы он выполнялся через цикл и многократно заполнял объект пользовательского класса, а затем изменял его на NSData перед добавлением каждого из них в массив, будет ли это иметь большую проблему с производительностью, чем просто передача нормальных типов данных в массив?
Rob85
66

Мне кажется довольно расточительным бегать по массиву и кодировать объекты в NSData самостоятельно. Ваша ошибка BC_Person is a non-property-list objectговорит вам, что фреймворк не знает, как сериализовать ваш объект person.

Таким образом, все, что нужно, это убедиться, что ваш объект person соответствует 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")
}

И вуаля, вы сохранили массив пользовательских объектов в NSUserDefaults

Даниэль Галаско
источник
Этот ответ твердый! Мои объекты соответствовали NSCoder, но я забыл о массиве, в котором они хранились. Спасибо, сэкономил мне много часов!
Микаэль Хеллман
1
@Daniel, я просто вставил твой код как есть в игровую площадку xcode 7.3, и он выдает ошибку «let retrievedPeople: [Person] = retrievePeople ()!» -> EXC_BAD_INSTRUCTION (код = EXC_I386_INVOP, субкод = 0x0). Какие корректировки мне нужно сделать? Спасибо
рок-молот
1
@rockhammer выглядит так, как будто NSUserDefaults не работает на игровых площадках, так как Xcode 7 :(
обновится в
1
@ Даниэль, нет проблем. Я пошел вперед и вставил ваш код в мой проект, и он работает как шарм! Мой класс объекта имеет NSDate в дополнение к 3 типам String. Мне просто нужно было заменить NSDate на String в функции декодирования. Спасибо
рок-молот
Для Swift3:func encode(with aCoder: NSCoder)
Ачинтя Ашок
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"];
jose920405
источник
34

Swift 3 Solution

Простой служебный класс

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 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 лучшая быстрая адаптация +1
Габо Куадрос Карденас
developer.apple.com/documentation/foundation/userdefaults/… ... «этот метод не нужен и не должен использоваться». LOL ... кто-то в Apple не командный игрок.
Всезнайка Бунц
12

Swift-4 Xcode 9.1

попробуй этот код

вы не можете хранить маппер в 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 для нового метода, который выдает ошибку. Возможно, обновление вашего кода? :)
Марси
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значения. Поэтому я просто скопировал словарь в изменяемый словарь, удалил нули и затем сохранилNSUserDefaults

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

В этом случае я знал, какие ключи могут быть NSNullзначениями.

simple_code
источник
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
}

Пример пользовательской модели подтверждения Codable

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

Как это использовать

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

//GetValue
print(GlobalSettings.user)
Димо Хэмди
источник
Это лучший СОВРЕМЕННЫЙ ОТВЕТ. Stacker, используйте этот ответ является действительно масштабируемым.
Стефан Васильевич
4

Swift 5: Кодируемый протокол может быть использован вместо NSKeyedArchiever .

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

Структура Pref - это пользовательская оболочка вокруг стандартного объекта 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 {...
Prcela
источник
1
if let data = UserDefaults.standard.data(forKey: keyUser) {И, между прочим, просто бессмысленно Userделать Userэтоreturn try JSONDecoder().decode(User.self, from: data)
Лео Дабус
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"
Шакил Ахмед
источник
это только для кодируемой структуры
Кишор Кумар
Я получаю: 'archivedData (withRootObject :)' устарел в macOS 10.14: Использовать + archivedDataWithRootObject: требуетсяSecureCoding: ошибка: вместо этого
множество людей