プロパティリスト以外のオブジェクトをNSUserDefaultsとして設定しようとしました


196

このエラーの原因はわかっていると思いましたが、何が問題なのか理解できません。

ここに私が得ている完全なエラーメッセージがあります:

プロパティリスト以外のオブジェクトを設定しようとしました(
   「<BC_Person:0x8f3c140>」
)キーpersonDataArrayのNSUserDefaults値として

私はプロトコルに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;
}

アプリの中でいくつかの点で、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メソッドの実装は役に立ちません。あなただけのようなもの格納することができNSArrayNSDictionaryNSStringNSDataNSNumber、とNSDateではNSUserDefaults

オブジェクトをNSData(一部のコードにあるように)に変換して、に保存する必要NSDataがありNSUserDefaultsます。あなたも保存できるNSArrayNSDataあなたがする必要がある場合を。

配列を読み戻すときはNSDataBC_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 2013年

え?コード行を同じコード行に置き換えたいので、タイプミスを犯したと思います。私のコードは、エンコードされたオブジェクトの配列をユーザーのデフォルトに配置します。
rmaddy 2013年

NSArrayはuserDefaultsでしか使用できないと思っていたので、私は迷っていたと思いますが、例ではNSMutable配列を使用しているのがわかります。多分私は何かを理解していないだけかもしれません...
icekomo

2
NSMutableArray伸びNSArrayます。NSMutableArrayを期待する任意のメソッドにを渡すのはまったく問題ありませんNSArray。実際に格納されている配列は、NSUserDefaults読み戻すと不変になることに注意してください。
rmaddy 2013年

1
これはパフォーマンスにコストがかかりますか?たとえば、これがループを介して実行され、カスタムクラスオブジェクトに何回も入力され、それをNSDataに変更してから、それぞれを配列に追加した場合、通常のデータ型を配列に渡すよりもパフォーマンスに問題がありますか?
Rob85

66

配列を実行してオブジェクトをNSDataに自分でエンコードするのは、私にとってはかなり無駄なようです。あなたのエラーBC_Person is a non-property-list objectは、フレームワークが個人オブジェクトをシリアル化する方法を知らないことを伝えています。

したがって、必要なのは、人物オブジェクトがNSCodingに準拠していることを確認することだけで、カスタムオブジェクトの配列をNSDataに変換し、それをデフォルトに格納するだけです。ここに遊び場があります:

編集NSUserDefaultsXcode 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
@Daniel、私はあなたのコードをそのまま遊び場のxcode 7.3に貼り付けたところ、「let retrievePeople:[Person] = retrievePeople()!」というエラーが発生しました。-> EXC_BAD_INSTRUCTION(コード= EXC_I386_INVOP、サブコード= 0x0)。どのような調整が必要ですか?ありがとう
ロックハンマー

1
@rockhammerはNSUserDefaultsがPlaygroundで機能しないようです。Xcode7 :(がまもなく更新されます
Daniel Galasko

1
@ダニエル、問題ありません。私は先に進んであなたのコードを私のプロジェクトに挿入しました、そしてそれは魅力のように機能します!私のオブジェクトクラスには、3つのString型に加えてNSDateがあります。デコード関数で文字列をNSDateに置き換える必要がありました。ありがとう
ロックハンマー

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 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
Gabo Cuadros Cardenas 2017

developer.apple.com/documentation/foundation/userdefaults/… ...「このメソッドは不要であり、使用すべきではありません。」笑...アップルの誰かはチームプレイヤーではありません。
オタクバンツ2018

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ため、あと1つステップです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

辞書をに保存しようとしてこの問題が発生しましたNSUserDefaultsNSNull値が含まれているため、保存できないことがわかりました。そのため、辞書を変更可能な辞書にコピーして、nullを削除し、次に保存しましたNSUserDefaults

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

この場合、どのキーがNSNull値であるかがわかりました。


4

Swift 5:NSKeyedArchieverの代わりにCodableプロトコルを使用できます。

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

県の構造体は、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) {そしてところでからのキャストUserには、Userただ無意味ですreturn try JSONDecoder().decode(User.self, from: data)
レオDabus

4

スウィフト@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)

これは最高の現代の答えです。スタッカー、この答えは本当に拡張可能です。
Stefan Vasiljevic

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"

これはコード化可能な構造体専用です
Kishore Kumar

「archivedData(withRootObject :)」はmacOS 10.14で廃止されました:代わりに+ archivedDataWithRootObject:requiringSecureCoding:error:を使用してください
multitudes
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.