NSUserDefaults에 사용자 정의 객체를 저장하는 방법


268

좋아, 그래서 나는 주위에 약간의 파고를하고 있었고, 나는 내 문제를 알고 있지만 그것을 고치는 방법을 모른다. 일부 데이터를 보유하기 위해 사용자 정의 클래스를 만들었습니다. 이 수업을 위해 물건을 만들고 세션 사이에 지속될 필요가 있습니다. 모든 정보를에 넣기 전에는 NSUserDefaults작동하지 않습니다.

-[NSUserDefaults setObject:forKey:]: Attempt to insert non-property value '<Player: 0x3b0cc90>' of class 'Player'.

사용자 정의 클래스 "Player"를에 넣을 때 나타나는 오류 메시지 NSUserDefaults입니다. 이제는 분명히 NSUserDefaults일부 유형의 정보 만 저장 한다는 것을 읽었습니다 . 어떻게 물체를 넣을 수 NSUSerDefaults있습니까?

내 사용자 지정 개체를 "인코딩"하여 넣는 방법이 있어야한다는 것을 읽었지만 구현 방법을 잘 모릅니다. 도움을 주시면 감사하겠습니다. 감사합니다!

****편집하다****

좋아, 그래서 아래 주어진 코드로 작업했습니다 (감사합니다!),하지만 여전히 문제가 있습니다. 기본적으로 코드가 이제 충돌하고 오류가 발생하지 않기 때문에 이유를 모르겠습니다. 아마도 나는 기본적인 것을 놓치고 너무 피곤하지만, 우리는 보게 될 것입니다. 내 사용자 정의 클래스 "Player"의 구현은 다음과 같습니다.

@interface Player : NSObject {
    NSString *name;
    NSNumber *life;
    //Log of player's life
}
//Getting functions, return the info
- (NSString *)name;
- (int)life;


- (id)init;

//These are the setters
- (void)setName:(NSString *)input; //string
- (void)setLife:(NSNumber *)input; //number    

@end

구현 파일 :

#import "Player.h"
@implementation Player
- (id)init {
    if (self = [super init]) {
        [self setName:@"Player Name"];
        [self setLife:[NSNumber numberWithInt:20]];
        [self setPsnCounters:[NSNumber numberWithInt:0]];
    }
    return self;
}

- (NSString *)name {return name;}
- (int)life {return [life intValue];}
- (void)setName:(NSString *)input {
    [input retain];
    if (name != nil) {
        [name release];
    }
    name = input;
}
- (void)setLife:(NSNumber *)input {
    [input retain];
    if (life != nil) {
        [life release];
    }
    life = input;
}
/* This code has been added to support encoding and decoding my objecst */

-(void)encodeWithCoder:(NSCoder *)encoder
{
    //Encode the properties of the object
    [encoder encodeObject:self.name forKey:@"name"];
    [encoder encodeObject:self.life forKey:@"life"];
}

-(id)initWithCoder:(NSCoder *)decoder
{
    self = [super init];
    if ( self != nil )
    {
        //decode the properties
        self.name = [decoder decodeObjectForKey:@"name"];
        self.life = [decoder decodeObjectForKey:@"life"];
    }
    return self;
}
-(void)dealloc {
    [name release];
    [life release];
    [super dealloc];
}
@end

그래서 그것은 제 수업입니다. 꽤 직설적입니다. 나는 그것이 물건을 만드는 데 효과가 있다는 것을 알고 있습니다. AppDelegate 파일의 관련 부분은 다음과 같습니다 (암호화 및 암호 해독 기능이라고 함).

@class MainViewController;

@interface MagicApp201AppDelegate : NSObject <UIApplicationDelegate> {
    UIWindow *window;
    MainViewController *mainViewController;
}

@property (nonatomic, retain) IBOutlet UIWindow *window;
@property (nonatomic, retain) MainViewController *mainViewController;

-(void)saveCustomObject:(Player *)obj;
-(Player *)loadCustomObjectWithKey:(NSString*)key;


@end

그리고 구현 파일의 중요한 부분 :

    #import "MagicApp201AppDelegate.h"
    #import "MainViewController.h"
    #import "Player.h"

    @implementation MagicApp201AppDelegate


    @synthesize window;
    @synthesize mainViewController;


    - (void)applicationDidFinishLaunching:(UIApplication *)application {
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
        //First check to see if some things exist
        int startup = [prefs integerForKey:@"appHasLaunched"];
        if (startup == nil) {
//Make the single player 
        Player *singlePlayer = [[Player alloc] init];
        NSLog([[NSString alloc] initWithFormat:@"%@\n%d\n%d",[singlePlayer name], [singlePlayer life], [singlePlayer psnCounters]]); //  test
        //Encode the single player so it can be stored in UserDefaults
        id test = [MagicApp201AppDelegate new];
        [test saveCustomObject:singlePlayer];
        [test release];
}
[prefs synchronize];
}

-(void)saveCustomObject:(Player *)object
{ 
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    [prefs setObject:myEncodedObject forKey:@"testing"];
}

-(Player *)loadCustomObjectWithKey:(NSString*)key
{
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    NSData *myEncodedObject = [prefs objectForKey:key ];
    Player *obj = (Player *)[NSKeyedUnarchiver unarchiveObjectWithData: myEncodedObject];
    return obj;
}

Eeee, 모든 코드에 대해 죄송합니다. 그냥 도와 주려고 기본적으로 앱이 시작된 후 즉시 충돌합니다. 앱의 암호화 부분으로 좁혔습니다. 충돌이있는 곳이므로 뭔가 잘못하고 있지만 확실하지 않습니다. 다시 한 번 도움을 주셔서 감사합니다.

(아직 암호화 작업을하지 않았으므로 아직 암호 해독을하지 않았습니다.)


충돌을 일으키는 행 번호와 같은 충돌에 대한 스택 추적 또는 추가 정보가 있습니까? 코드에 문제가 즉시 나타나지 않으므로 시작점이 도움이 될 것입니다.
chrissr

위의 예제에서 encodeObject를 사용하여 int 인 self.life를 저장했습니다. 대신 encodeInt를 사용해야합니다.
broot

답변:


516

Player 클래스에서 다음 두 가지 메서드를 구현합니다 (자신의 객체와 관련된 것으로 encodeObject에 대한 호출을 대체 함).

- (void)encodeWithCoder:(NSCoder *)encoder {
    //Encode properties, other class variables, etc
    [encoder encodeObject:self.question forKey:@"question"];
    [encoder encodeObject:self.categoryName forKey:@"category"];
    [encoder encodeObject:self.subCategoryName forKey:@"subcategory"];
}

- (id)initWithCoder:(NSCoder *)decoder {
    if((self = [super init])) {
        //decode properties, other class vars
        self.question = [decoder decodeObjectForKey:@"question"];
        self.categoryName = [decoder decodeObjectForKey:@"category"];
        self.subCategoryName = [decoder decodeObjectForKey:@"subcategory"];
    }
    return self;
}

읽고 쓰기 NSUserDefaults:

- (void)saveCustomObject:(MyObject *)object key:(NSString *)key {
    NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    [defaults setObject:encodedObject forKey:key];
    [defaults synchronize];

}

- (MyObject *)loadCustomObjectWithKey:(NSString *)key {
    NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
    NSData *encodedObject = [defaults objectForKey:key];
    MyObject *object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
    return object;
}

뻔뻔스럽게 빌린 코드 : 클래스를 nsuserdefaults에 저장


변경 사항을 반영하도록 위의 게시물을 수정했습니다.
Ethan Mick

@chrissr NSUserDefaults defaults = [NSUserDefaults standardUserDefaults];에 오류가 있습니다. ... NSUserDefaults * defaults 여야합니다.
매기

2
NSKeyedArchiver는 ... NSArray 또는 NSDictionary 객체로 자동으로 내려 가서 인코딩 가능한 사용자 정의 객체를 인코딩합니다.
BadPirate

2
나는 독창적이지 않고 단지 진정한 의문이 아니며, init 메소드에서 self.property 인 합성 세터를 사용하는 사과 지침에 반하지 않습니까?
Samhan Salahuddin

2
@chrissr 변경하십시오 NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];위해 NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];. 변수가 일치하지 않습니다.
GoRoS

36

encodeWithCoder 및 initWithCoder를 구현하는 것이 매우 지루하기 때문에 라이브러리 RMMapper ( https://github.com/roomorama/RMMapper )를 만들어 NSUserDefaults에 사용자 정의 객체를 더 쉽고 편리하게 저장할 수 있습니다.

클래스를 보관 가능으로 표시하려면 다음을 사용하십시오. #import "NSObject+RMArchivable.h"

NSUserDefaults에 사용자 정의 객체를 저장하려면

#import "NSUserDefaults+RMSaveCustomObject.h"
NSUserDefaults* defaults = [NSUserDefaults standardUserDefaults];
[defaults rm_setCustomObject:user forKey:@"SAVED_DATA"];

NSUserDefaults에서 커스텀 obj를 얻으려면 :

user = [defaults rm_customObjectForKey:@"SAVED_DATA"]; 

라이브러리는 유지하려는 모든 것에 대한 속성이 있고 속성이있는 모든 것을 유지하려는 것으로 가정합니다. 이것이 사실이라면 도서관이 확실히 도움이 될 수 있지만 많은 경우에 그렇지 않다는 것을 알게 될 것입니다.
Erik B

일반 객체 만 유지하는 것이 유용합니다. Java의 Gson과 사용법이 매우 비슷하다고 말하고 싶습니다. 어떤 경우를보고 있습니까?
thomasdao

7
정말 도움이되었습니다. 답을 계속 읽게되어 기쁩니다. D
Albara

내 사용자 정의 객체가 다른 사용자 정의 객체의 속성에있을 때 어떻습니까?
Shial

@Shial : 다른 사용자 정의 개체 보관 가능한을 할 경우, 그것은 너무 저장됩니다
thomasdao

35

스위프트 4

이러한 종류의 작업에 대한 모든 마법을 수행 하는 Codable 프로토콜을 도입했습니다 . 커스텀 구조체 / 클래스를 준수하십시오.

struct Player: Codable {
  let name: String
  let life: Double
}

그리고 Defaults에 저장하기 위해 PropertyListEncoder / Decoder를 사용할 수 있습니다 :

let player = Player(name: "Jim", life: 3.14)
UserDefaults.standard.set(try! PropertyListEncoder().encode(player), forKey: kPlayerDefaultsKey)

let storedObject: Data = UserDefaults.standard.object(forKey: kPlayerDefaultsKey) as! Data
let storedPlayer: Player = try! PropertyListDecoder().decode(Player.self, from: storedObject)

이러한 객체의 배열 및 기타 컨테이너 클래스에서도 이와 같이 작동합니다.

try! PropertyListDecoder().decode([Player].self, from: storedArray)

1
매력처럼 작동
Nathan Barreto

Codable모든 인스턴스 멤버가 자신 인 경우 에만 무료로 동작을 얻을 수 있습니다. Codable그렇지 않으면 직접 인코딩 코드를 작성해야합니다.
BallpointBen

1
또는 단순히 해당 멤버 유형 Codable도 준수하십시오 . 그리고 재귀 적으로. 매우 맞춤화 된 경우에만 인코딩 코드를 작성해야합니다.
Sergiu Todirascu

Swift 4를 사용하는 가장 쉬운 방법
alstr

31

빠른 버전을 찾는 사람이 있다면 :

1) 데이터에 대한 사용자 정의 클래스를 만듭니다.

class customData: NSObject, NSCoding {
let name : String
let url : String
let desc : String

init(tuple : (String,String,String)){
    self.name = tuple.0
    self.url = tuple.1
    self.desc = tuple.2
}
func getName() -> String {
    return name
}
func getURL() -> String{
    return url
}
func getDescription() -> String {
    return desc
}
func getTuple() -> (String,String,String) {
    return (self.name,self.url,self.desc)
}

required init(coder aDecoder: NSCoder) {
    self.name = aDecoder.decodeObjectForKey("name") as! String
    self.url = aDecoder.decodeObjectForKey("url") as! String
    self.desc = aDecoder.decodeObjectForKey("desc") as! String
}

func encodeWithCoder(aCoder: NSCoder) {
    aCoder.encodeObject(self.name, forKey: "name")
    aCoder.encodeObject(self.url, forKey: "url")
    aCoder.encodeObject(self.desc, forKey: "desc")
} 
}

2) 데이터를 저장하려면 다음 기능을 사용하십시오.

func saveData()
    {
        let data  = NSKeyedArchiver.archivedDataWithRootObject(custom)
        let defaults = NSUserDefaults.standardUserDefaults()
        defaults.setObject(data, forKey:"customArray" )
    }

3) 검색하려면

if let data = NSUserDefaults.standardUserDefaults().objectForKey("customArray") as? NSData
        {
             custom = NSKeyedUnarchiver.unarchiveObjectWithData(data) as! [customData]
        }

참고 : 여기에 사용자 정의 클래스 객체의 배열을 저장하고 검색합니다.


9

@ chrissr의 대답을 취하고 실행하면이 코드는 멋진 범주로 구현되어 NSUserDefaults사용자 정의 객체를 저장하고 검색 할 수 있습니다.

@interface NSUserDefaults (NSUserDefaultsExtensions)

- (void)saveCustomObject:(id<NSCoding>)object
                     key:(NSString *)key;
- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key;

@end


@implementation NSUserDefaults (NSUserDefaultsExtensions)


- (void)saveCustomObject:(id<NSCoding>)object
                     key:(NSString *)key {
    NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    [self setObject:encodedObject forKey:key];
    [self synchronize];

}

- (id<NSCoding>)loadCustomObjectWithKey:(NSString *)key {
    NSData *encodedObject = [self objectForKey:key];
    id<NSCoding> object = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];
    return object;
}

@end

용법:

[[NSUserDefaults standardUserDefaults] saveCustomObject:myObject key:@"myKey"];

7

스위프트 3

class MyObject: NSObject, NSCoding  {
    let name : String
    let url : String
    let desc : String

    init(tuple : (String,String,String)){
        self.name = tuple.0
        self.url = tuple.1
        self.desc = tuple.2
    }
    func getName() -> String {
        return name
    }
    func getURL() -> String{
        return url
    }
    func getDescription() -> String {
        return desc
    }
    func getTuple() -> (String, String, String) {
        return (self.name,self.url,self.desc)
    }

    required init(coder aDecoder: NSCoder) {
        self.name = aDecoder.decodeObject(forKey: "name") as? String ?? ""
        self.url = aDecoder.decodeObject(forKey: "url") as? String ?? ""
        self.desc = aDecoder.decodeObject(forKey: "desc") as? String ?? ""
    }

    func encode(with aCoder: NSCoder) {
        aCoder.encode(self.name, forKey: "name")
        aCoder.encode(self.url, forKey: "url")
        aCoder.encode(self.desc, forKey: "desc")
    }
    }

저장하고 검색하려면 :

func save() {
            let data  = NSKeyedArchiver.archivedData(withRootObject: object)
            UserDefaults.standard.set(data, forKey:"customData" )
        }
        func get() -> MyObject? {
            guard let data = UserDefaults.standard.object(forKey: "customData") as? Data else { return nil }
            return NSKeyedUnarchiver.unarchiveObject(with: data) as? MyObject
        }

그냥 알림 : self변수 전에 의무적으로하지 않습니다 initencode.
Tamás Sengel

당신이 NSCoder와 객체를 intializing하는 방법 당신은 보여줄 수
Abhishek Thapliyal을

@AbhishekThapliyal, 나는 당신을 이해하지 못합니다. init (코더 aDecoder : NSCoder)가 초기화 함
Vyacheslav

6

NSUserDefaults에 저장 한 데이터 / 객체 동기화

-(void)saveCustomObject:(Player *)object
{ 
    NSUserDefaults *prefs = [NSUserDefaults standardUserDefaults];
    NSData *myEncodedObject = [NSKeyedArchiver archivedDataWithRootObject:object];
    [prefs setObject:myEncodedObject forKey:@"testing"];
    [prefs synchronize];
}

이것이 도움이되기를 바랍니다. 감사


미래의 사람들은 Swift 3부터 "동기화"를 호출해서는 안된다는 점에 유의하십시오.
호세 라미레즈

@JozemiteApps 왜? 또는이 주제에 대한 설명이 포함 된 링크를 게시 할 수 있습니까?
code4latte

@ code4latte 변경되었는지는 모르겠지만 Apple의 설명서에 따르면 "자동으로 주기적으로 호출되는 synchronize () 메서드는 메모리 내 캐시를 사용자의 기본 데이터베이스와 동기화 상태로 유지합니다." 전에는 즉시 저장된 데이터가 필요한 경우에만 호출해야한다고 말했습니다. 나는 사용자가 더 이상 전화해서는 안된다는 몇 시간을 읽었습니다.
Jose Ramirez
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.