Хранение настраиваемых объектов в NSMutableArray в NSUserDefaults

101

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

Я попытался преобразовать 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 или необработанный 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
Стефан, ты спасатель, посмотри сюда; Эта строка спасла мне голову «if (self = [super init])» stackoverflow.com/questions/1933285/…
fyasar
У меня ERROR_BAD_ACCESS, но вы меня спасли. Использовал свойство только с сохранением, и добавление self.variable решило его.
Дэвид
0

Я думаю, что проблема с вашим кодом не в сохранении массива результатов. Его загрузка данных попробуйте использовать

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

Это вернет массив, указанный ключом.

Gcoop
источник
0

См. «Почему NSUserDefaults не удалось сохранить NSMutableDictionary в iPhone SDK?» ( Почему NSUserDefaults не удалось сохранить NSMutableDictionary в iPhone SDK? )


Если вы хотите (де) сериализовать пользовательские объекты, вы должны предоставить функции для (де) сериализации данных (протокол NSCoding). Решение, на которое вы ссылаетесь, работает с массивом int, потому что массив является не объектом, а непрерывным фрагментом памяти.

f3lix
источник
Я думаю, что вы правы, я посмотрю на NSCoding (если у вас нет рекомендованной ссылки), сами объекты состоят из NSStrings, и все, поэтому я полагаю, что я мог бы просто написать свой собственный сериализатор и поместить их все в массив строк
Энтони Мэйн,
Хорошо, я реализовал NSCode, см. Выше, но до сих пор без удовольствия использовать NSArchiver
Энтони Мэйн
0

Я бы не рекомендовал хранить подобные вещи в db по умолчанию.

SQLite довольно легко подобрать и использовать. В одном из моих скринкастов ( http://pragprog.com/screencasts/v-bdiphone ) есть эпизод про написанную мною простую обертку (код можно получить, не покупая SC).

Хранить данные приложения в пространстве приложения намного проще.

Все, что сказано, если все еще имеет смысл поместить эти данные в базу данных по умолчанию, см. Сообщение f3lix.

Билл Дадни
источник
1
Спасибо за предложение, но я действительно не хочу начинать писать SQL-запросы (как я вижу в вашем примере) только для объекта с несколькими строками
Энтони Мэйн