비 속성 목록 객체를 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방법은 도움이되지 않습니다. 당신은 같은 것들을 저장할 수있다 NSArray, NSDictionary, NSString, NSData, NSNumber,과 NSDate에서를 NSUserDefaults.

당신은에 개체를 변환 할 필요가 NSData있음을 (당신이 코드의 일부가 같은) 저장 NSData에서 NSUserDefaults. 당신은 심지어 저장할 수 NSArrayNSData당신이 필요로하는 경우입니다.

배열을 읽을 때 객체를 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

userDefaults와 함께 NSArray 만 사용할 수 있다고 생각하여 길을 잃은 것 같지만 예제에서 NSMutable 배열을 사용하고 있습니다. 어쩌면 내가 ... 그냥 이해 뭔가를 해요
icekomo

2
NSMutableArray확장 NSArray합니다. NSMutableArray를 기대하는 모든 메소드 에 전달하는 것이 NSArray좋습니다. 실제로 저장된 어레이는 NSUserDefaults다시 읽을 때 변경할 수 없습니다.
rmaddy 2013

1
이로 인해 성능이 저하됩니까? 예를 들어 이것이 루프를 통해 실행되고 사용자 정의 클래스 객체를 여러 번 채우고 각 객체를 배열에 추가하기 전에 NSData로 변경하면 일반 데이터 유형을 배열에 전달하는 것보다 성능 문제가 더 커 집니까?
Rob85

66

배열을 통해 객체를 NSData로 직접 인코딩하는 것은 다소 낭비 적입니다. 귀하의 오류 BC_Person is a non-property-list object는 프레임 워크가 person 객체를 직렬화하는 방법을 모른다는 것을 알려줍니다.

따라서 개인 개체가 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를 준수하는 객체가 있었지만 저장된 위치에 대해 잊어 버렸습니다. 감사합니다. 많은 시간을 절약했습니다!
Mikael Hellman

1
@Daniel, 방금 놀이터 xcode 7.3에 코드를 붙여 넣었으며 "letetedPeople : [Person] = retrievePeople ()!"에 오류가 발생했습니다. -> EXC_BAD_INSTRUCTION (코드 = EXC_I386_INVOP, 하위 코드 = 0x0). 어떤 조정을해야합니까? 감사합니다
rockhammer 2016 년

1
@rockhammer는 Xcode 7 이후 NSUserDefaults가 Playgrounds에서 작동하지 않는 것 같습니다. (곧 업데이트 될 예정
Daniel Galasko 2016 년

1
@Daniel, 문제 없습니다. 계속해서 코드를 프로젝트에 삽입했는데 매력처럼 작동합니다! 내 객체 클래스에는 3 개의 문자열 유형 외에도 NSDate가 있습니다. 디코드 기능에서 NSDate를 String으로 대체해야했습니다. 감사합니다
rockhammer

Swift3 경우 :func encode(with aCoder: NSCoder)
Achintya 쇼크

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

스위프트 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

developer.apple.com/documentation/foundation/userdefaults/… ... "이 방법은 필요하지 않으며 사용해서는 안됩니다." LOL ... apple의 누군가는 팀 플레이어가 아닙니다.
Nerdy Bunz

12

스위프트 -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에서 더 이상 사용되지 않습니다. 아마도 코드가 업데이트 되었습니까? :)
Marcy

10

우선, rmaddy의 답변 (위)이 옳습니다. 구현 NSCoding하는 것이 도움이되지 않습니다. 그러나 NSCoding사용하기 위해 NSKeyedArchiver모든 것을 구현해야 하므로 한 번만 더 변환하면 via를 통해 변환 할 수 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값 이 될 수 있는지 알았습니다 .


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) {및 Btw는에서 캐스팅 User을하는 것은 User무의미 그냥return try JSONDecoder().decode(User.self, from: data)
레오 버스들이시길

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

스위프트 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 구조체입니다
키 쇼어 쿠마르

내가 얻을 : 'archivedData (withRootObject :'맥 OS 10.14 사용되지 않습니다 : 사용 + archivedDataWithRootObject : requiringSecureCoding : 오류 : 대신
무리
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.