Lưu trữ các đối tượng tùy chỉnh trong một NSMutableArray trong NSUserDefaults


101

Gần đây tôi đã cố gắng lưu trữ kết quả tìm kiếm của ứng dụng iPhone của mình trong bộ sưu tập NSUserDefaults. Tôi cũng sử dụng điều này để lưu thành công thông tin đăng ký của người dùng, nhưng vì một số lý do cố gắng lưu trữ NSMutableArray của tôi về các lớp Vị trí tùy chỉnh luôn bị trống.

Tôi đã thử chuyển đổi NSMutableArray thành phần tử NSData kể từ bài đăng này nhưng tôi nhận được kết quả tương tự ( Có thể lưu mảng số nguyên bằng NSUserDefaults trên iPhone không? )

Các mẫu mã tôi đã thử là:

Tiết kiệm:

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

hoặc là

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

hoặc là

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

Tải:

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

hoặc là

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

hoặc là

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

Sau khi làm theo lời khuyên bên dưới, tôi cũng đã triển khai NSCoder trong đối tượng của mình (bỏ qua việc lạm dụng NSString tạm thời của nó):

#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"];

}

Câu trả lời:


177

Để tải các đối tượng tùy chỉnh trong một mảng, đây là những gì tôi đã sử dụng để lấy mảng:

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];
}

Bạn nên kiểm tra xem dữ liệu trả về từ mặc định của người dùng không phải là nil, vì tôi tin rằng việc hủy lưu trữ từ nil sẽ gây ra sự cố.

Việc lưu trữ rất đơn giản, sử dụng mã sau:

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

Như f3lix đã chỉ ra, bạn cần phải làm cho đối tượng tùy chỉnh của mình tuân thủ giao thức NSCoding. Thêm các phương pháp như sau sẽ thực hiện thủ thuật:

- (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
Bạn đã thiếu "return self" ở cuối phương thức initWithCoder:. Thêm điều đó dường như cho phép việc hủy lưu trữ diễn ra.
Brad Larson

2
Bạn không nên lưu trữ các mảng thành nsuserdefaults vì các mảng có thể phát triển quá lớn đối với nsuserdefaults. Thay vào đó, nó phải được lưu trữ thành một tệp bằng đường dẫn + (BOOL) archiveRootObject: (id) rootObject toFile: (NSString *). Tại sao câu trả lời này lại được bình chọn nhiều như vậy?
mskw 24/12/12

1
@mskw - Bạn nói đúng rằng NSUserDefaults không nên được sử dụng để lưu trữ các mảng lớn (Dữ liệu lõi hoặc SQLite thô nên được sử dụng cho điều đó thay thế), nhưng hoàn toàn tốt để lưu trữ các mảng nhỏ gồm các đối tượng được mã hóa ở đó. Trong mọi trường hợp, câu hỏi đặt ra là làm thế nào để thực hiện việc này, đó là những gì đoạn mã trên cung cấp. Theo như phiếu bầu, dự đoán của bạn cũng tốt như tôi. Rất nhiều người chắc hẳn đã muốn làm điều này trong bốn năm qua.
Brad Larson

2
@Goodsum - Không, nó hoạt động tốt. Hãy thử nó, nó sẽ trả về một NSMutableArray thực sự có thể thay đổi được. Nó chỉ bắt đầu mảng mới bằng cách thêm mảng mục đó vào nó.
Brad Larson

1
@AlbertRenshaw - andSyncBạn đã thêm vào đoạn mã trên đến từ đâu? Đó không phải là chữ ký phương thức thực cho NSUserDefaults. Ngoài ra, synchronizekhông còn hữu ích như trước đây: twitter.com/Catfish_Man/status/647274106056904704
Brad Larson

13

Tôi nghĩ rằng bạn đã gặp lỗi trong phương thức initWithCoder của mình, ít nhất là trong mã được cung cấp, bạn không trả về đối tượng '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


}

tôi sử dụng

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];
}

và nó hoạt động hoàn hảo đối với tôi.

Trân trọng,

Stefan


3
Stephan bạn là người cứu mạng, hãy nhìn vào đây; Dòng lưu tâm trí của tôi "if (tự = [siêu init])" stackoverflow.com/questions/1933285/...
fyasar

Tôi nhận được ERROR_BAD_ACCESS nhưng bạn đã cứu tôi. Việc sử dụng thuộc tính chỉ giữ lại và thêm biến self.variable đã giải quyết được nó.
David

0

Tôi nghĩ rằng có vẻ như vấn đề với mã của bạn là không lưu mảng kết quả. Đang tải dữ liệu thử dùng

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

Điều này sẽ trả về mảng được chỉ định bởi khóa.


0

Xem "Why NSUserDefaults failed to save NSMutableDictionary in iPhone SDK?" ( Tại sao NSUserDefaults không lưu được NSMutableDictionary trong iPhone SDK? )


Nếu bạn muốn (de) tuần tự hóa các đối tượng tùy chỉnh, bạn phải cung cấp các chức năng để (de) tuần tự hóa dữ liệu (giao thức NSCoding). Giải pháp bạn đề cập đến hoạt động với mảng int vì mảng không phải là một đối tượng mà là một đoạn bộ nhớ liền kề.


Tôi nghĩ quyền của bạn, tôi sẽ xem xét NSCoding (trừ khi bạn có liên kết được đề xuất), bản thân các đối tượng được tạo thành trên NSStrings, vì vậy tôi cho rằng tôi chỉ có thể viết bộ tuần tự của riêng mình và đặt tất cả chúng vào một mảng chuỗi
Anthony Main

Ok vì vậy tôi đã triển khai NSCode xem ở trên, nhưng vẫn không hài lòng khi sử dụng NSArchiver
Anthony Main

0

Tôi khuyên bạn không nên cố gắng lưu trữ những thứ như thế này trong db mặc định.

SQLite khá dễ chọn và sử dụng. Tôi có một tập trong một trong các chương trình truyền hình của tôi ( http://pragprog.com/screencasts/v-bdiphone ) về một trình bao bọc đơn giản mà tôi đã viết (bạn có thể lấy mã mà không cần mua SC).

Lưu trữ dữ liệu ứng dụng trong không gian ứng dụng gọn gàng hơn nhiều.

Tất cả những gì đã nói nếu vẫn có ý nghĩa khi đặt dữ liệu này vào db mặc định, thì hãy xem bài đăng mà f3lix đã đăng.


1
Cảm ơn bạn đã gợi ý nhưng tôi không thực sự muốn phải bắt đầu viết các truy vấn SQL (như tôi thấy trong ví dụ của bạn) chỉ cho một đối tượng có một vài chuỗi
Anthony Main
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.