Re: copy & isEqual nightmares
Re: copy & isEqual nightmares
- Subject: Re: copy & isEqual nightmares
- From: Uli Kusterer <email@hidden>
- Date: Mon, 13 Feb 2012 10:14:35 +0100
On 13.02.2012, at 01:25, James Maxwell wrote:
> But what I don't get is how hash plays into all this. I've always read that I have to override hash when I override isEqual, but I don't exactly understand what that means or does. What I've done is to make a hash that is equal when two objects have the same values for propA and propB (ignoring propC). That's how I interpreted the few threads I've read about overriding hash...
A hash is a gatekeeper, a criterion used to discard some of a large number of expensive comparisons. The only condition a hash has to fulfill is that if the hashes are different, the objects *must not* be equal. If the hashes are identical, the objects may or may not be equal.
It *has to be* like this. Your data consists of probably 8 or 16 bytes, maybe more. A hash consists of about 4 bytes. So by that alone, if you go through all the permutations of bits in your 16 bytes (all possible values your object may contain), some of these must map to the same 4-byte-hash. You only have 4 billion different hash values, after all. This is what is called a "hash collision": Two different objects that result in the same hash.
When looking for an object identical to another, the hash is simply used to eliminate the majority of objects, who *do* have a different hash. A good hashing function gives you different hashes for the most common values, while rarer values cause more hash collisions. That way, the majority can be quickly discarded, leaving you to *really* compare only a few leftover objects that happen to collide.
It's simply a speed optimization in that case.
> But what I need to do in my code, right now, is to copy an object, then alter some properties of the copy, but not the original -- a typical reason for wanting a copy. But I'm seeing that the copy has the same hash and appears to be the same object, since changing properties of the copy changes the original.
How are you copying this object? What do the instance variables of this object contain? The -copy method is not magic. If you have any instance variables that are pointers (like objects), you *have to* override -copyWithZone: to make copies of those objects as well, if they are mutable. E.g. NSString is immutable, so you usually replace an old NSString value with a new one, so you can just -retain it, but if it's an NSMutableString, then you need to copy that, or both copies of *your* object, even though different, will reference (and thus edit) the same mutable string object.
Collection classes usually perform a shallow copy. That is, if you copy an NSMutableDictionary, it will copy the dictionary (so you can modify the list without modifying the original list), but will simply retain the objects in the array. This is more efficient and usually what's desired, but if you are duplicating your entire dictionary hoping to be able to independently edit all of your objects in there, you'll have to do it differently and make a deep copy yourself.
> So does the system see the matching hashes as identical objects, not just the collection classes?
No. Try it: Call NSLog( @"%p", theObject ); to log the actual memory address of theObject (or just pause in the debugger and note down both addresses). Unless you made a shallow copy of a containing object, you will get different addresses logged. The hashes will be identical, of course. That's the whole point of hashes. If the memory address is identical, it *must* be the same object (and NSDictionary probably checks for that by default and considers the objects equal right away). Otherwise, it looks at the hash, and if it's different, the objects can't be equal. And only if it's neither of those cases, it actually compares ivars using -isEqual:.
> Do I have to manually implement some sort of deepCopy method?
For containers, yes. For your object, -copyWithZone: should be sufficient. Or you can grab one from https://github.com/uliwitness/UliKit/ e.g. NSDictionary+DeepCopy.m.
> Or, can I just change hash, so that the copy is a different instance, without losing the functionality I need for collections? I don't understand why I'd need to make a deepCopy, since that's what copy is for...
The hash should be calculated from your object's values. If your object's ivars (those that are relevant to the hash) can be changed after creation, you must always recalculate your hash whenever one of them is modified (or mark it as invalid, and lazily recalculate it whenever someone next asks for the hash). If it really is the same object, you goofed and are somehow not copying your object. In that case changing the hash will not do much good, as you'll change the hash for both places that reference this single object. If you have two, the moment you change a value in it, the hash should change as well. If they have identical values, they should have identical hashes.
Cheers,
-- Uli Kusterer
"The Witnesses of TeachText are everywhere..."
http://www.masters-of-the-void.com
_______________________________________________
Cocoa-dev mailing list (email@hidden)
Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com
Help/Unsubscribe/Update your Subscription:
This email sent to email@hidden