Зберігання власних об'єктів у NSMutableArray в NSUserDefaults


101

Нещодавно я намагався зберігати результати пошуку свого додатка iPhone у колекції NSUserDefaults. Я також використовую це для успішного збереження інформації про реєстрацію користувача, але чомусь намагаючись зберігати мій NSMutableArray спеціальних класів розташування завжди повертається порожнім.

Я спробував перетворити NSMutableArray в елемент NSData, як на цій посаді, але я отримав такий же результат ( Можливо зберегти цілий масив за допомогою NSUserDefaults на iPhone? )

Зразки коду, які я спробував:

Зберегти:

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

Ви повинні перевірити, що дані, повернені за замовчуванням користувача, не є нульовими, тому що я вважаю, що забороняється дію з нуля викликає збій.

Архівація проста, використовуючи наступний код:

[[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
Ви пропустили "return self" в кінці вашого initWithCoder: методу. Додавання, що, здається, дозволяє нездійснюватися.
Бред Ларсон

2
Не слід зберігати масиви в nsuserdefaults, оскільки масиви можуть зрости занадто великими для nsuserdefaults. Замість цього його слід зберігати у файлі, використовуючи + (BOOL) archiveRootObject: (id) rootObject toFile: (NSString *) шлях. Чому за цю відповідь так голосують?
mskw

1
@mskw - Ви маєте рацію в тому, що NSUserDefaults не слід використовувати для зберігання великих масивів (для цього слід використовувати Core Data або raw SQLite), але цілком чудово зберігати там невеликі масиви закодованих об’єктів. У будь-якому випадку, питання ставило питання, як це зробити, що передбачено вищевказаним кодом. Що стосується голосів, то ваша здогадка така ж добра, як і моя. Багато людей, мабуть, хотіли це зробити протягом останніх чотирьох років.
Бред Ларсон

2
@Goodsum - Ні, це працює чудово. Спробуйте, він поверне NSMutableArray, який дійсно є змінним. Він просто запускає новий масив, додаючи до цього масив елементів.
Бред Ларсон

1
@AlbertRenshaw - звідки andSyncви взяли доданий вище код? Це не фактичний підпис методу для NSUserDefaults. Крім того, synchronizeне так корисно, як це було раніше: twitter.com/Catfish_Man/status/647274106056904704
Бред Ларсон

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
Стефане, ти рятувальник життя, дивись тут; Ця лінія врятувала мій розум «якщо (сам = [супер INIT])» stackoverflow.com/questions/1933285 / ...
fyasar

Я отримав ERROR_BAD_ACCESS, але ти врятував мене. Використовував властивість лише з функцією збереження та додав рішення.
Девід

0

Я думаю, схоже, що проблема з вашим кодом полягає не в збереженні масиву результатів. Для завантаження даних спробуйте використовувати

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

Це поверне масив, визначений ключем.


0

Див. "Чому NSUserDefaults не вдалося зберегти NSMutableDictionary в iPhone SDK?" ( Чому NSUserDefaults не вдалося зберегти NSMutableDictionary в iPhone SDK? )


Якщо ви хочете (де) серіалізувати власні об’єкти, ви повинні надати функції (де) серіалізувати дані (протокол NSC кодування). Рішення, на яке ви посилаєтесь, працює з масивом int, оскільки масив - це не об'єкт, а суміжний фрагмент пам'яті.


Я думаю, що ваше право я роздивлюсь NSCoding (якщо у вас немає рекомендованого посилання), самі об'єкти складаються з NSStrings, це все так, я думаю, я міг би просто написати власний серіалізатор і вкласти їх у рядковий масив
Ентоні Майн

Гаразд, я реалізував NSCode див. Вище, але все ще не раді, використовуючи NSArchiver
Anthony Main

0

Я б рекомендував не намагатися зберігати подібні речі у налаштуваннях за замовчуванням.

Підбирати та використовувати SQLite досить просто. У мене є епізод в одному з моїх екранографій ( http://pragprog.com/screencasts/v-bdiphone ) про просту оболонку, яку я написав (код можна отримати, не купуючи ПК).

Набагато чистіше зберігати дані програми у просторі додатків.

Все, що було сказано, якщо все-таки є сенс розмістити ці дані у налаштуваннях за замовчуванням, тоді перегляньте публікацію f3lix.


1
Дякую за пропозицію, але мені дуже не хочеться починати писати запити SQL (як я бачу у вашому прикладі) лише для об'єкта з кількома рядками
Ентоні Майн
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.