NSUserDefaults and Conditional Encoding of Custom Objects in an NSArray
I know there are a lot of posts out there concerning the problem of how to archive custom objects in an NSArray or NSMutableArray and save them in NSUserDefaults. Conforming to the NSCoding Protocol and saving to NSUserDefaults isn't problematic and I use NSUserDefaults quite a lot to store the user-submitted data in my app - it mostly contains objects representing a Person (let's call the NSObject subclass "Person") which can have multiple objects of the NSObject subclass "Property" stored in an NSMutableArray. Therefore, the data structure looks like this:
NSMutableArray "persons":
Person "aPerson":
NSMutableArray "properties":
Property "aProperty"
Property "anotherProperty"
Person "anotherPerson:
...
Archiving and restoring the information was not problematic at first, because both Person and Property conform to the NSCoding Protocol - but now a problem occured which I was not able to solve yet despite those thousands of google requests in the last couple days ;)
Some of the Property objects contain references to other Persons ("Participants", which are linked to the same property and are contained in an NSMutableArray). When I store the whole data to NSUserDefaults using NSKeyedArchiver, I use
[aCoder encodeObject:participants forKey:@"participants"];
in the Property's "encodeWithCoder" method to archive the NSMutableArray "participants" which stores the references to other Person objects. But when I decode those Person objects, they are created new and separated from the Person objects that already exist somewhere else. The NSMutableArray "participants" only contains references, weak links to the Person objects and should therefore conditional encode its content, as one can do with other objects manually in "encodeWithCoder":
开发者_高级运维[aCoder encodeConditionalObject:anObject forKey:aKey];
When the NSMutableArray gets decoded, it should represent a list of references to already existing Person objects - not completely new ones! The test "aPerson==[[aDecoder decodeObjectForKey:@"participants"] objectAtIndex:0]" is currently returning NO although it had returned YES before the encoding/decoding process has taken place.
I hope my explanation is somehow understandable and you can help me with my problem :) In simple words: How can I conditional encode custom objects contained in an NSMutableArray?
Thank You !
If NSMutableArray
would use encodeConditionalObject:forKey:
for the objects it contains, it would just mean that those objects aren't encoded at all, if they're not encoded unconditionally somewhere else in your object graph. This wouldn't help you in this case (the array would just be empty).
The problem is that you cannot really encode references to objects in memory. An object reference is basically just a pointer to an address in memory. When you start your app the next time and create the very same object (whether by unarchiving or otherwise), it will almost definitely have a different address in memory. There is no way the unarchiver can 'magically' know, which existing object corresponds to the reference it has archived, because the memory address (the object's 'identity') loses all its meaning when you quit your app.
You have to use other means of identifying your objects, such as database row IDs, dictionary keys, etc. and establish the connection of the archived key and the existing object corresponding to that key manually.
I had an issue with this too. I have objects that have an array of weak links to other objects. I know all the objects linked to will be encoded, so I just want to make sure I can rebuild the links.
With a single weak link is it possible to use:
aCoder.encodeConditionalObject(thing, forKey: "Thing")
...and if that item has already been encoded from elsewhere, then a reference to that encoded item will be used.
But, what to do if you have an array full of 'conditional' items, where the array needs to be encoded unconditionally?
I ended up wrapping the items I want to link to.
class thingLink: NSObject, NSCoding
{
weak var thing: Thing?
init(_ thing: Thing) {
self.thing = thing
}
required init?(coder aDecoder: NSCoder) {
thing = aDecoder.decodeObject(forKey: "Thing") as? Thing
}
func encode(with aCoder: NSCoder) {
// We encode these conditionally as they must be used elsewhere
aCoder.encodeConditionalObject(thing, forKey: "Thing")
}
}
...then I store these in my array which I encode as usual.
aCoder.encode(things, forKey: "Things")
If I move to a database to store things, I think this will help there too, because I will need a separate table to store the links and maintain priority etc.
精彩评论