tags:

views:

174

answers:

4

Hi,

I have an objective-c class which contains a pointer to another class. I want to archive an instance of this class via NSCoder:

@interface Barn
{
    int m_numHorses;

    // Barn does not allocate this instance, it just points to it.
    Farmer* m_pFarmer;
}
@end

...

- (void)encodeWithCoder:(NSCoder *)encoder
{
    [encoder encodeInt:m_numHorses forKey:@"numHorses"];
    [encoder encode?:m_pFarmer forKey:@"pFarmer"];
}

- (void) setPointer:(Farmer*)pFarmer
{
    m_pFarmer = pFarmer;
}

How would I archive the m_pFarmer pointer? It doesn't make sense to me as all it is is an address, and I don't see what NSCoder could serialize to disk for you such that it knows how to restore the link later on when you deserialize?

Thanks Thanks

+2  A: 

You should use encodeObject: and implement NSCoding in your Farmer class as well, so it gets called recursively.

pgb
Ah sorry I should have been more clear - m_pFarmer is simply a pointer to a Farmer object allocated elsewhere in the system - it is not owned by the Barn class instance -
Are you storing the Farmer object outside of this code? If you are not, it makes sense to me to save it recursively.
pgb
+1  A: 

You can't directly encode a pointer, because when you unarchive the objects, the pointer's value is going to be entirely different. I mean, you could store it with

encodeValueOfObjCType:@encode(id) at:&m_pFarmer

but there's no guarantee that the deserialized pointer will point to the deserialized farmer; in fact, it's very very likely that it won't.

If Barn doesn't own the Farmer, then Barn shouldn't recreate it on deserialization; you'll end up with a new Farmer that's not separate from the original one. What you need, then, is a way to find the deserialized instance of the original Farmer, and replace Barn's instance of the Farmer with the other one.

Somebody owns the Farmer, right? So Barn needs to have a findMyFarmer method, which looks through all the FarmerOwners and finds the original instance it should be using. (Maybe by comparing a farmerID ivar on the Farmer?) Once that's done, you can implement -[NSObject awakeAfterUsingCoder:(NSCoder *)] on the Barn to trigger a farmer-replacement routine.

Hope that makes sense. Look at the documentation on Archiving and Serialization, especially the page on Encoding and Decoding Objects to see more about replacing objects on the fly.

Update

NSArchivers and NSKeyedArchivers support the idea of conditional archiving, (via encodeConditionalObject:) in which the object is added to the archive only if some other object in the archive has already added it. The documentation says "Typically, conditional objects are used to encode weak, or non-retained, references to objects.". So if your Farmer is being archived already, then you'd want to add it, but if you're only encoding your Barn without any farmer, then you wouldn't want to.

Definitely check out the documentation referenced above.

BJ Homer
+1  A: 

You should first read the docs that BJ points to.

The serialization libraries are smart enough to detect repeats of the same object, and will only encode it one time. So in general, you should be encoding this object if you need it later. The serialization libraries can even handle graph loops. You do not need to worry about these things, and actively should not try to second-guess how other objects are going to handle this object. ObjC does not have the kind of strong object ownership concepts that C++ often requires. Reference counted memory management does not require it, and you shouldn't recreate it.

Speaking of reference counting, do you mean not to retain Farmer in your setter? Is this because Barn is already retaining Farmer? Or is this garbage collected code? Or was this an unintentional under-retain? This could definitely cause a crash when you deserialize this stuff if you've actually under-retained it.

Rob Napier
A: 

If you literally want to encode the address held by the pointer (because you can guarantee the Farmer object will stay at that location until you decode the archive) then you can encode the pointer as an NSInteger.

i.e. [encoder encodeInteger:(NSInteger)m_pFarmer forKey:@"pFarmer"];

Generally speaking though, this is a bad idea unless you are very careful to hold onto the Farmer object longer than the archive.

Matt Gallagher