Хранение пользовательских объектов в NSMutableArray в NSUserDefaults
Недавно я пытался сохранить результаты поиска своего 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"];
}
Ответы
Ответ 1
Для загрузки пользовательских объектов в массив это то, что я использовал для захвата массива:
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];
}
Вы должны проверить, что данные, возвращенные с пользовательских значений по умолчанию, не ноль, потому что я считаю, что unarchiving из 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;
}
Ответ 2
Я думаю, что вы получили ошибку в вашем методе 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
См. "Почему NSUserDefaults не удалось сохранить NSMutableDictionary в iPhone SDK?" (Почему NSUserDefaults не удалось сохранить NSMutableDictionary в iPhone SDK?)
Если вы хотите (де) сериализовать пользовательские объекты, вам необходимо предоставить функции (де) сериализации данных (протокол NSCoding). Решение, на которое вы ссылаетесь, работает с массивом int, потому что массив не является объектом, а смежным куском памяти.
Ответ 4
Я думаю, что проблема с вашим кодом не заключается в сохранении массива результатов. При загрузке данных попробуйте использовать
lastResults = [prefs arrayForKey:@"lastResults"];
Это вернет массив, указанный ключом.
Ответ 5
Я бы рекомендовал не пытаться хранить такие вещи, как это в db по умолчанию.
SQLite довольно легко подбирать и использовать. У меня есть эпизод в одном из моих скринкастов (http://pragprog.com/screencasts/v-bdiphone) о простой обертке, которую я написал (вы можете получить код, не покупая СК).
Это намного проще хранить данные приложения в пространстве приложения.
Все, что сказал, если все-таки имеет смысл помещать эти данные в db по умолчанию, см. публикацию post f3lix.