NSCoding of NSMutableDictionaries containing custom objects
Asked Answered
S

3

22

I was trying to serialize a SearchEntity object(custom object) containing an NSMutableDictionary containing a set of type CategoryEntity(custom object).

1 SearchEntity<NSCoding> containing: 1 NSMutableDictionary (parameters) parameters containing X CategoryEntities<NSCoding> containing just strings and numbers.

At this line [encoder encodeObject:parameters forKey:kPreviousSearchEntityKey]; in the SearchEntity encodeWithCoder" I get GDB:Interrupted every time, no error message, exception etc. just GDB:Interrupted.

This is the implementation in SearchEntity and parameters is the NSMutableDictionary

#pragma mark -
#pragma mark NSCoding delegate methods

- (void) encodeWithCoder:(NSCoder*)encoder 
{
    //encode all the values so they can be persisted in NSUserdefaults
    if (parameters) 
        [encoder encodeObject:parameters forKey:kPreviousSearchEntityKey]; //GDB:Interrupted!
}

- (id) initWithCoder:(NSCoder*)decoder 
{
    if (self = [super init]) 
    {
        //decode all values to return an object from NSUserdefaults in the same state as when saved     
        [self setParameters:[decoder decodeObjectForKey:kPreviousSearchEntityKey]];
    }
    return self;
}

The CategoryEntity also implements the NSCoding protocol and looks like this:

- (void) encodeWithCoder:(NSCoder*)encoder 
{
    //encode all the values so they can be persisted in NSUserdefaults
    [encoder encodeObject:ID forKey:kIDKey];
    [encoder encodeObject:text forKey:kTextKey];
    [encoder encodeObject:category forKey:kCategoryKey];
    [encoder encodeObject:categoryIdentifierKey forKey:kCategoryIdentifierKey];
}

- (id) initWithCoder:(NSCoder*)decoder 
{
    if (self = [super init]) {

        //decode all values to return an object from NSUserdefaults in the same state as when saved
        [self setID:[decoder decodeObjectForKey:kIDKey]];
        [self setText:[decoder decodeObjectForKey:kTextKey]];
        [self setCategory:[decoder decodeObjectForKey:kCategoryKey]];
        [self setCategoryIdentifierKey:[decoder decodeObjectForKey:kCategoryIdentifierKey]];
    }
    return self;
}

I try to encode it from a wrapper for NSUserDefaults, like this:

+ (void) setPreviousSearchParameters:(SearchParameterEntity*) entity
{
    if (entity) 
    {
        //first encode the entity (implements the NSCoding protocol) then save it
        NSData *encodedObject = [NSKeyedArchiver archivedDataWithRootObject:entity];
        [[self defaults] setObject:encodedObject forKey:kPreviousSearchKey];
        [[self defaults] synchronize];      
    }
}

+ (SearchParameterEntity*) getPreviousSearchParameters
{
    //retrieve the encoded NSData object that was saved, decode and return it
    SearchParameterEntity *entity = nil;
    NSData *encodedObject = [[self defaults] objectForKey:kPreviousSearchKey];

    if (encodedObject)
        entity = [NSKeyedUnarchiver unarchiveObjectWithData:encodedObject];

    return entity;
}

I was thinking that when I ask to Serialize the SearchEntity, it would start to serialize the 'parameters' mutableDictionary object, NSCoder will call "encode" on the CategoryEntities contained in the dictionary and they will all respond with their correct encoded objects.

However I just get GDB:Interrupted in the bottom of the console.

How can I debug this?

And is my approach wrong, should I wrap all levels of encoding in NSData?

Ps. I do the exact same thing with a ResultEntity containing NSArrays of CategoryEntities, it encodes with no problems, so I guess the NSMutableDictionary is the only thing sticking out.

Sateen answered 3/12, 2010 at 9:23 Comment(5)
Do you have more information available about the crash? Mutable objects are supported by NSKeyed(un)Archiver. The crash is likely caused by something else.Poppied
Try typing continue into the console - you may need to do this a couple of times before the crash / stack trace is shown.Oppidan
Did you look for zombie objects? I usually find that this kind of inexplicable errors, that appear on code that looks just fine, is due to zombies.Lourielouse
What are your keys? Be mindful that they must be NSStrings.Twofold
An answer to a similar question lists some interesting cavaets relevant to your question: (a) keys of the dictionary must be NSString and (b) while you can persist mutable objects they will be restored as immutable equivalents.Modred
M
1

The code you have posted does not appear to be incorrect. I've made a best guess at some details you've left out and I get a successful result from a test program containing your code with enough boilerplate to show that it encodes/decodes correctly.

(You can compile it from the command line using: gcc -framework foundation test.m -o test and run with: ./test.)

With regard to your question, how can I debug this, I would suggest an approach as follows:

  • (Temporarily) modify your code to be as simple as possible. For example, you could change the parameters property to a plain NSString and verify that works correctly first.
  • Slowly add in complexity, introducing one new property at a time, until the error starts occurring again. Eventually you will narrow down where the troublesome data is coming from.

Alas, if this is occurring due to some mis-managed memory elsewhere in your app, debugging this code itself may not get you anywhere. Try (manually) verifying that memory is managed correctly for each piece of data you are receiving for encoding.

If you are already using Core Data you could consider persisting just the object ID in the user defaults and restore your object graph based on that. (See: Archiving NSManagedObject with NSCoding).

Modred answered 3/10, 2011 at 5:37 Comment(0)
G
0

I suggest you to bypass the NSMutableArray first. Let SearchEntity contains only one CategoryEntity and see if it works.

The code you posted looks good, you may want to give us more detailed context.

For object encoding, this file may help: DateDetailEntry

Gilbertine answered 4/11, 2011 at 3:57 Comment(0)
C
-1

The problem with archiving objects with NSKeyedArchiver is that you cannot encode mutable objects. Only instances of NSArray, NSDictionary, NSString, NSDate, NSNumber, and NSData (and some of their subclasses) can be serialized

So, in your SearchEntity method encodeWithCoder: you should try creating NSDictionary from NSMutableDictionary and then encoding the immutable one:

if (parameters) {
    NSDictionary *dict = [NSDictionary dictionaryWithDictionary:parameters];
    [encoder encodeObject:dict forKey:kPreviousSearchEntityKey];
}

Also in the initWithCoder: method try creating NSMutableDictionary from the encoded immutable one:

NSDictionary *dict = [decoder decodeObjectForKey:kPreviousSearchEntityKey];
     [self setParameters:[NSMutableDictionary dictionaryWithDictionary:dict]];

Also check for all obejct within parameters dictionary to conform to NSCoding protocol and ensure that all of them encode only immutable objects in their encodeWithCoder: methods.

Hope it solves the problem.

Carlsen answered 22/9, 2011 at 6:50 Comment(2)
NSMutableDictionary and NSMutableArray both implement NSCoding.Acinaciform
Agree, but this was the problem in my case, exactly the same thing, and when I tryied encoding immutable objects it was fixed. I found in documentation that only imutable objects can be serialized.Carlsen

© 2022 - 2024 — McMap. All rights reserved.