NSUserDefaults의 NSMutableArray에 사용자 지정 개체 저장


101

최근에 내 iPhone 앱의 검색 결과를 NSUserDefaults 컬렉션에 저장하려고했습니다. 또한 이것을 사용하여 사용자 등록 정보를 성공적으로 저장하지만 어떤 이유로 사용자 지정 위치 클래스의 NSMutableArray를 저장하려고하면 항상 비어있게됩니다.

이 게시물에서 NSMutableArray를 NSData 요소로 변환하려고 시도했지만 동일한 결과를 얻었습니다 ( iPhone에서 NSUserDefaults를 사용하여 정수 배열을 저장할 수 있습니까? ).

내가 시도한 코드 샘플은 다음과 같습니다.

저장:

[prefs setObject:results forKey:@"lastResults"];
[prefs synchronize];

또는

NSData *data = [NSData dataWithBytes:&results length:sizeof(results)];
[prefs setObject:data forKey:@"lastResults"];

또는

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];

하중:

lastResults = (NSMutableArray *)[prefs objectForKey:@"lastResults"];

또는

NSData *data = [prefs objectForKey:@"lastResults"];
memcpy(&lastResults, data.bytes, data.length);  

또는

NSData *data = [prefs objectForKey:@"lastResults"];
lastResults = [NSKeyedUnarchiver unarchiveObjectWithData:data];

아래의 조언을 따르면 내 개체에 NSCoder를 구현했습니다 (임시 NSString의 남용 무시).

#import "Location.h"


@implementation Location

@synthesize locationId;
@synthesize companyName;
@synthesize addressLine1;
@synthesize addressLine2;
@synthesize city;
@synthesize postcode;
@synthesize telephoneNumber;
@synthesize description;
@synthesize rating;
@synthesize priceGuide;
@synthesize latitude;
@synthesize longitude;
@synthesize userLatitude;
@synthesize userLongitude;
@synthesize searchType;
@synthesize searchId;
@synthesize distance;
@synthesize applicationProviderId;
@synthesize contentProviderId;

- (id) initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.locationId = [coder decodeObjectForKey:@"locationId"];
        self.companyName = [coder decodeObjectForKey:@"companyName"];
        self.addressLine1 = [coder decodeObjectForKey:@"addressLine1"];
        self.addressLine2 = [coder decodeObjectForKey:@"addressLine2"];
        self.city = [coder decodeObjectForKey:@"city"];
        self.postcode = [coder decodeObjectForKey:@"postcode"];
        self.telephoneNumber = [coder decodeObjectForKey:@"telephoneNumber"];
        self.description = [coder decodeObjectForKey:@"description"];
        self.rating = [coder decodeObjectForKey:@"rating"];
        self.priceGuide = [coder decodeObjectForKey:@"priceGuide"];
        self.latitude = [coder decodeObjectForKey:@"latitude"];
        self.longitude = [coder decodeObjectForKey:@"longitude"];
        self.userLatitude = [coder decodeObjectForKey:@"userLatitude"];
        self.userLongitude = [coder decodeObjectForKey:@"userLongitude"];
        self.searchType = [coder decodeObjectForKey:@"searchType"];
        self.searchId = [coder decodeObjectForKey:@"searchId"];
        self.distance = [coder decodeObjectForKey:@"distance"];
        self.applicationProviderId = [coder decodeObjectForKey:@"applicationProviderId"];
        self.contentProviderId = [coder decodeObjectForKey:@"contentProviderId"];
    }
}

- (void) encodeWithCoder: (NSCoder *)coder
{
    [coder encodeObject:locationId forKey:@"locationId"];
    [coder encodeObject:companyName forKey:@"companyName"];
    [coder encodeObject:addressLine1 forKey:@"addressLine1"];
    [coder encodeObject:addressLine2 forKey:@"addressLine2"];
    [coder encodeObject:city forKey:@"city"];
    [coder encodeObject:postcode forKey:@"postcode"];
    [coder encodeObject:telephoneNumber forKey:@"telephoneNumber"];
    [coder encodeObject:description forKey:@"description"];
    [coder encodeObject:rating forKey:@"rating"];
    [coder encodeObject:priceGuide forKey:@"priceGuide"];
    [coder encodeObject:latitude forKey:@"latitude"];
    [coder encodeObject:longitude forKey:@"longitude"];
    [coder encodeObject:userLatitude forKey:@"userLatitude"];
    [coder encodeObject:userLongitude forKey:@"userLongitude"];
    [coder encodeObject:searchType forKey:@"searchType"];
    [coder encodeObject:searchId forKey:@"searchId"];
    [coder encodeObject:distance forKey:@"distance"];
    [coder encodeObject:applicationProviderId forKey:@"applicationProviderId"];
    [coder encodeObject:contentProviderId forKey:@"contentProviderId"];

}

답변:


177

배열에 사용자 지정 개체를로드하는 경우 다음과 같이 배열을 가져 왔습니다.

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"savedArray"];
if (dataRepresentingSavedArray != nil)
{
    NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
    if (oldSavedArray != nil)
        objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
    else
        objectArray = [[NSMutableArray alloc] init];
}

nil에서 아카이브를 해제하면 충돌이 발생한다고 믿기 때문에 사용자 기본값에서 반환 된 데이터가 nil이 아닌지 확인해야합니다.

보관은 다음 코드를 사용하여 간단합니다.

[[NSUserDefaults standardUserDefaults] setObject:[NSKeyedArchiver archivedDataWithRootObject:objectArray] forKey:@"savedArray"];

f3lix가 지적했듯이 사용자 지정 개체가 NSCoding 프로토콜을 준수하도록해야합니다. 다음과 같은 메소드를 추가하면 트릭을 수행 할 수 있습니다.

- (void)encodeWithCoder:(NSCoder *)coder;
{
    [coder encodeObject:label forKey:@"label"];
    [coder encodeInteger:numberID forKey:@"numberID"];
}

- (id)initWithCoder:(NSCoder *)coder;
{
    self = [super init];
    if (self != nil)
    {
        label = [[coder decodeObjectForKey:@"label"] retain];
        numberID = [[coder decodeIntegerForKey:@"numberID"] retain];
    }   
    return self;
}

3
initWithCoder : 메소드 끝에 "return self"가 누락되었습니다. 이를 추가하면 보관 해제가 발생할 수 있습니다.
Brad Larson

2
어레이가 nsuserdefaults에 비해 너무 커질 수 있으므로 어레이를 nsuserdefaults에 저장하지 마십시오. 대신 + (BOOL) archiveRootObject : (id) rootObject toFile : (NSString *) path를 사용하여 파일에 저장해야합니다. 이 답변이 왜 그렇게 많은 투표를 받았습니까?
mskw 2012

1
@mskw-NSUserDefaults를 큰 배열의 저장에 사용해서는 안된다는 점이 맞지만 (대신 Core Data 또는 원시 SQLite를 사용해야 함) 인코딩 된 개체의 작은 배열을 거기에 숨겨두는 것이 완벽합니다. 어쨌든 질문은 위의 코드가 제공하는 것입니다. 투표에 관한 한, 당신의 추측은 나만큼 좋습니다. 많은 사람들이 지난 4 년 동안 이것을하고 싶었을 것입니다.
Brad Larson

2
@Goodsum-아니요, 잘 작동합니다. 그것을 시도하면 실제로 변경 가능한 NSMutableArray를 반환합니다. 항목 배열을 추가하여 새 배열을 시작합니다.
Brad Larson

1
@AlbertRenshaw- andSync위 코드에 추가 한 것은 어디에서 왔습니까? NSUserDefaults에 대한 실제 메서드 서명이 아닙니다. 또한 synchronize예전만큼 유용하지 않습니다. twitter.com/Catfish_Man/status/647274106056904704
Brad Larson

13

initWithCoder 메서드에서 오류가 발생했다고 생각합니다. 최소한 제공된 코드에서는 'self'개체를 반환하지 않습니다.

- (id) initWithCoder: (NSCoder *)coder
{
    if (self = [super init])
    {
        self.locationId = [coder decodeObjectForKey:@"locationId"];
        self.companyName = [coder decodeObjectForKey:@"companyName"];
        self.addressLine1 = [coder decodeObjectForKey:@"addressLine1"];
        self.addressLine2 = [coder decodeObjectForKey:@"addressLine2"];
        self.city = [coder decodeObjectForKey:@"city"];
        self.postcode = [coder decodeObjectForKey:@"postcode"];
        self.telephoneNumber = [coder decodeObjectForKey:@"telephoneNumber"];
        self.description = [coder decodeObjectForKey:@"description"];
        self.rating = [coder decodeObjectForKey:@"rating"];
        self.priceGuide = [coder decodeObjectForKey:@"priceGuide"];
        self.latitude = [coder decodeObjectForKey:@"latitude"];
        self.longitude = [coder decodeObjectForKey:@"longitude"];
        self.userLatitude = [coder decodeObjectForKey:@"userLatitude"];
        self.userLongitude = [coder decodeObjectForKey:@"userLongitude"];
        self.searchType = [coder decodeObjectForKey:@"searchType"];
        self.searchId = [coder decodeObjectForKey:@"searchId"];
        self.distance = [coder decodeObjectForKey:@"distance"];
        self.applicationProviderId = [coder decodeObjectForKey:@"applicationProviderId"];
        self.contentProviderId = [coder decodeObjectForKey:@"contentProviderId"];
    }

    return self; // this is missing in the example above


}

나는 사용한다

NSData *data = [NSKeyedArchiver archivedDataWithRootObject:results];
[prefs setObject:data forKey:@"lastResults"];

NSUserDefaults *currentDefaults = [NSUserDefaults standardUserDefaults];
NSData *dataRepresentingSavedArray = [currentDefaults objectForKey:@"lastResults"];
if (dataRepresentingSavedArray != nil)
{
        NSArray *oldSavedArray = [NSKeyedUnarchiver unarchiveObjectWithData:dataRepresentingSavedArray];
        if (oldSavedArray != nil)
                objectArray = [[NSMutableArray alloc] initWithArray:oldSavedArray];
        else
                objectArray = [[NSMutableArray alloc] init];
}

저에게 완벽하게 작동합니다.

안부,

스테판


3
스테판 당신은 생명의 은인입니다. 여기를보세요. 그 줄은 내 마음을 구했습니다. "if (self = [super init])" stackoverflow.com/questions/1933285/…
fyasar

나는 ERROR_BAD_ACCESS를 받았지만 당신이 나를 구했습니다. 보유만으로 속성을 사용하고 self.variable을 추가하면 해결되었습니다.
데이비드

0

코드의 문제가 결과 배열을 저장하지 않는 것 같습니다. 데이터를로드하려면

lastResults = [prefs arrayForKey:@"lastResults"];

이것은 키로 지정된 배열을 반환합니다.


0

"NSUserDefaults가 iPhone SDK에 NSMutableDictionary를 저장하지 못한 이유는 무엇입니까?"를 참조하십시오. ( NSUserDefaults가 iPhone SDK에 NSMutableDictionary를 저장하지 못한 이유는 무엇입니까? )


사용자 지정 개체를 (역) 직렬화하려면 데이터 (NSCoding 프로토콜)를 (역) 직렬화하는 함수를 제공해야합니다. 참조하는 솔루션은 배열이 객체가 아니라 연속적인 메모리 청크이기 때문에 int 배열과 함께 작동합니다.


나는 당신의 권리가 NSCoding을 살펴볼 것이라고 생각합니다 (권장 링키가없는 경우), 객체 자체는 모두 NSStrings로 구성되어 있으므로 내 직렬 변환기를 작성하고 모두 문자열 배열에 넣을 수 있다고 가정합니다.
Anthony Main

좋아, 그래서 나는 NSCode 위의 참조 구현,하지만 여전히 기쁨 NSArchiver를 사용하지
안토니 홈페이지를

0

나는 이와 같은 것을 기본 db에 저장하지 않는 것이 좋습니다.

SQLite는 선택하고 사용하기가 매우 쉽습니다. 내 스크린 캐스트 중 하나 ( http://pragprog.com/screencasts/v-bdiphone )에 내가 작성한 간단한 래퍼에 대한 에피소드가 있습니다 (SC를 구입하지 않고도 코드를 얻을 수 있음).

앱 공간에 앱 데이터를 저장하는 것이 훨씬 깔끔합니다.

이 데이터를 기본값 db에 넣는 것이 여전히 타당하다면 게시 된 f3lix 게시물을 참조하십시오.


1
제안 해 주셔서 감사합니다. 그러나 몇 개의 문자열이있는 객체에 대해서만 SQL 쿼리 작성을 시작하고 싶지 않습니다 (예제에서 볼 수 있음)
Anthony Main
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.