Работа с NSUserDefaults при сохранении собственного типа данных
Я читал эту тему Как сохранить мой тип данных в NSUserDefault? и получить оттуда эту полезную часть кода:
MyObject *myObject = [[MyObject alloc] init];
NSData *myObjectData = [NSData dataWithBytes:(void *)&myObject length:sizeof(myObject)];
[[NSUserDefaults standardUserDefaults] setObject:myObjectData forKey:@"kMyObjectData"];
для сохранения данных и это для чтения
NSData *getData = [[NSData alloc] initWithData:[[NSUserDefaults standardUserDefaults] objectForKey:@"kMyObjectData"]];
MyObject *getObject;
[getData getBytes:&getObject];
это работает очень хорошо, когда я сохраняю данные в одном ViewController
и прочитай это в другом. но когда я хочу использовать его в том же классе:
- (IBAction)linkedInLog:(UIButton *)sender
{
NSUserDefaults *myDefaults = [[NSUserDefaults standardUserDefaults] objectForKey:@"linkedinfo"];
NSData *getData = [[NSData alloc] initWithData:myDefaults];
LinkedContainer *getObject;
[getData getBytes:&getObject];
if (!myDefaults) {
mLogInView = [[linkedInLoginView alloc]initWithNibName:@"linkedInLogInView" bundle:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(loginViewDidFinish:)
name:@"loginViewDidFinish"
object:mLogInView];
[self.navigationController pushViewController:mLogInView animated:YES];
if ((FBSession.activeSession.isOpen)&&(mLinkedInIsLogegOn)) {
mMergeButton.hidden = NO;
}
}
else{
mLinkedInIsLogegOn= YES;
mLinkedInInfo.mConsumer = getObject.mConsumer;
mLinkedInInfo.mToken = getObject.mToken;
}
}
что-то идет не так. в @selector:loginViewDidFinish я сохраняю свои данные в NSUserDefaults
:
-(void) loginViewDidFinish:(NSNotification*)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
mLinkedInInfo.mConsumer = mLogInView.consumer;
mLinkedInInfo.mToken = mLogInView.accessToken;
NSData *myObjectData = [NSData dataWithBytes:(void *)&mLinkedInInfo length:sizeof(mLinkedInInfo)];
NSUserDefaults *lSave = [NSUserDefaults standardUserDefaults];
[lSave setObject:myObjectData forKey:@"linkedinfo"];
[lSave synchronize];
if (mLinkedInInfo.mToken) {
mLinkedInIsLogegOn = YES;
}
}
программа всегда падает, когда дело доходит до другой части. Если кто-то знает, что я делаю не так, пожалуйста, помогите мне)
сообщение об ошибке: Поток 1: EXC_BAD_ACCESS(код =2, адрес 0x8) при компиляции getObject.Consumer
2 ответа
В подавляющем большинстве случаев это не будет значимым способом сериализации вашего объекта в NSData:
MyObject *myObject = [[MyObject alloc] init];
NSData *myObjectData = [NSData dataWithBytes:(void *)&myObject length:sizeof(myObject)];
[[NSUserDefaults standardUserDefaults] setObject:myObjectData forKey:@"kMyObjectData"];
Каноническим способом сделать это было бы для MyObject принять протокол NSCoding. Исходя из кода, который вы разместили здесь, принятие NSCoding может выглядеть так:
- (id)initWithCoder:(NSCoder *)coder
{
if (self = [super init])
{
mConsumer = [coder decodeObjectForKey: @"consumer"];
mToken = [coder decodeObjectForKey: @"token"];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder
{
[coder encodeObject:mConsumer forKey: @"consumer"];
[coder encodeObject:mToken forKey:@"token"];
}
Выполнив эту работу, вы конвертируете MyObject в NSData и из нее следующим образом:
NSData* data = [NSKeyedArchiver archivedDataWithRootObject: myObject];
MyObject* myObject = (MyObject*)[NSKeyedUnarchiver unarchiveObjectWithData: data];
Код, который у вас есть, полностью разбивает стек и вылетает (потому что эта строка [getData getBytes:&getObject];
заставит NSData записывать байты по адресу getObject, который локально объявлен в стеке. Отсюда и разрушение стека. Начиная с вашего кода, работающая реализация может выглядеть примерно так:
- (IBAction)linkedInLog:(UIButton *)sender
{
NSData* dataFromDefaults = [[NSUserDefaults standardUserDefaults] objectForKey:@"linkedinfo"];
LinkedContainer* getObject = (LinkedContainer*)[NSKeyedUnarchiver unarchiveObjectWithData: dataFromDefaults];
if (!dataFromDefaults) {
mLogInView = [[linkedInLoginView alloc]initWithNibName:@"linkedInLogInView" bundle:nil];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(loginViewDidFinish:)
name:@"loginViewDidFinish"
object:mLogInView];
[self.navigationController pushViewController:mLogInView animated:YES];
if ((FBSession.activeSession.isOpen)&&(mLinkedInIsLogegOn)) {
mMergeButton.hidden = NO;
}
}
else{
mLinkedInIsLogegOn= YES;
mLinkedInInfo.mConsumer = getObject.mConsumer;
mLinkedInInfo.mToken = getObject.mToken;
}
}
-(void) loginViewDidFinish:(NSNotification*)notification
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
mLinkedInInfo.mConsumer = mLogInView.consumer;
mLinkedInInfo.mToken = mLogInView.accessToken;
NSData* objectData = [NSKeyedArchiver archivedDataWithRootObject: mLinkedInInfo];
[[NSUserDefaults standardUserDefaults] setObject: objectData forKey: @"linkedinfo"];
[[NSUserDefaults standardUserDefaults] synchronize];
if (mLinkedInInfo.mToken) {
mLinkedInIsLogegOn = YES;
}
}
Я согласен с ответом ipmcc, другой возможный вариант - добавить методы к вашему объекту, чтобы преобразовать его в NSDictionary
, Вы можете добавить методы к -initWithDictionary
также и должен сделать создание очень легким. Вытащить из словаря в NSUserDefaults
использовать, преобразовать в словарь, чтобы сохранить.
Вот пример этих двух методов с общими данными:
- (id)initWithDictionary:(NSDictionary *)dict
{
self = [super init];
// This check serves to make sure that a non-NSDictionary object
// passed into the model class doesn't break the parsing.
if(self && [dict isKindOfClass:[NSDictionary class]]) {
NSObject *receivedFences = [dict objectForKey:@"fences"];
NSMutableArray *parsedFences = [NSMutableArray array];
if ([receivedFences isKindOfClass:[NSArray class]]) {
for (NSDictionary *item in (NSArray *)receivedFences) {
if ([item isKindOfClass:[NSDictionary class]]) {
[parsedFences addObject:[Fences modelObjectWithDictionary:item]];
}
}
}
}
// More checks for specific objects here
return self;
}
- (NSDictionary *)dictionaryRepresentation
{
NSMutableDictionary *mutableDict = [NSMutableDictionary dictionary];
NSMutableArray *tempArrayForFences = [NSMutableArray array];
for (NSObject *subArrayObject in self.fences) {
if([subArrayObject respondsToSelector:@selector(dictionaryRepresentation)]) {
// This class is a model object
[tempArrayForFences addObject:[subArrayObject performSelector:@selector(dictionaryRepresentation)]];
} else {
// Generic object
[tempArrayForFences addObject:subArrayObject];
}
}
[mutableDict setValue:[NSArray arrayWithArray:tempArrayForFences] forKey:@"fences"];
return [NSDictionary dictionaryWithDictionary:mutableDict];
}
Это в основном стандартный код, который генерируется программой, которую я использую под названием JSON Accelerator. Он прочитает строку JSON, возвращенную API, и сгенерирует для вас объектный код. Не совсем новая концепция, но делает созданные классы для API очень простыми. И этот фрагмент кода отлично подходит для создания словарей, которые будут сохранены в NSUserDefaults
, Надеюсь это поможет.