4

i had read this topic How to save My Data Type in NSUserDefault? and get from there this useful part of code:

MyObject *myObject = [[MyObject alloc] init];

NSData *myObjectData  = [NSData dataWithBytes:(void *)&myObject length:sizeof(myObject)];

[[NSUserDefaults standardUserDefaults] setObject:myObjectData forKey:@"kMyObjectData"];

for saving data and this for reading

 NSData *getData = [[NSData alloc] initWithData:[[NSUserDefaults standardUserDefaults] objectForKey:@"kMyObjectData"]];

MyObject *getObject;

[getData getBytes:&getObject];

its works very good when i save data in one ViewController and read it in other. but when i whant to use it in the same class:

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

something going wrong. in @selector:loginViewDidFinish i am saving my data to 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;
    }   
}

the program always crashes when it comes to else part. If somebody knows what I am doing wrong please help me)

error message: Thread 1 : EXC_BAD_ACCESS(code=2,address 0x8) when compiling getObject.Consumer

Community
  • 1
  • 1
xaoc1993
  • 43
  • 5
  • you are sending incompatible pointer type to your `getData` – Ankur Arya Aug 29 '13 at 12:26
  • @Ankur can you explain what exactly is wrong? – xaoc1993 Aug 29 '13 at 12:36
  • I explained it in my answer. It's smashing the stack. This approach you've picked up from http://stackoverflow.com/questions/11788408/how-to-save-my-data-type-in-nsuserdefaul is an **awful, horrible** approach, and I've commented as such on that question. See my answer for details. – ipmcc Aug 29 '13 at 12:37

2 Answers2

4

In the vast majority of cases, this is not going to be a meaningful way to serialize your object into an NSData:

MyObject *myObject = [[MyObject alloc] init];

NSData *myObjectData  = [NSData dataWithBytes:(void *)&myObject length:sizeof(myObject)];

[[NSUserDefaults standardUserDefaults] setObject:myObjectData forKey:@"kMyObjectData"];

The canonical way to do this would be for MyObject to adopt the NSCoding protocol. Based on the code you posted here, an adoption of NSCoding might look like this:

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

Once you had done that work, you would convert MyObject to and from NSData like this:

NSData* data = [NSKeyedArchiver archivedDataWithRootObject: myObject];
MyObject* myObject = (MyObject*)[NSKeyedUnarchiver unarchiveObjectWithData: data];

The code you have here is totally going to smash the stack and crash (because this line [getData getBytes:&getObject]; will cause the NSData to write bytes to the address of getObject, which is locally declared on the stack. Hence stack smashing.) Starting from your code, a working implementation might look something like this:

- (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
  • 29,581
  • 5
  • 84
  • 147
  • Thank you it was very useful for me) everything working perfectly) – xaoc1993 Aug 29 '13 at 15:17
  • The OP (@xaoc1993) suggested [this edit](http://stackoverflow.com/review/suggested-edits/2820610) which was rejected by me. I did not realize it was the OP making the suggestion. ipmcc please update if it is correct. – chue x Aug 29 '13 at 15:31
3

I agree with ipmcc 's answer, another viable option would be to add methods to your object to convert it to an NSDictionary. You could add methods to -initWithDictionary as well and should make instantiation very easy. Pull from dictionary in NSUserDefaults to use, convert to dictionary to save.

Here is an example of those 2 methods with generic data:

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

This is basically boilerplate code that is generated by a program I use called JSON Accelerator. It will read a JSON string returned by an API and generate object code for you. Not really a new concept, but makes created classes for API's very easy. And this bit of code works great for creating dictionaries to be saved to NSUserDefaults. Hope this helps.

Bill Burgess
  • 14,054
  • 6
  • 49
  • 86
  • 1
    Put differently, if you don't want to implement `NSCoding` yourself, convert your object to a type that already does. – ipmcc Aug 29 '13 at 12:39
  • unfortunately i don't know how to implement NSCoding, now i'm reading about it and would try to implement it myself. – xaoc1993 Aug 29 '13 at 12:51
  • I provided an example of an NSCoding implementation based on your code in my answer. – ipmcc Aug 29 '13 at 13:02
  • i know but the type of mConsumer and mToken cannot be decoded too. so i had read something about it. so i should add NSCoding delegate to my class, write encodeWithCoder and initWithCoder methods and it should work&? – xaoc1993 Aug 29 '13 at 14:12