I have a small value class that I create lots of instances of. Often with the same value. This class is used as a kind of identifier, so the main use is comparing instances of this class with each other (via isEqual:
).
To save some memory and time comparing I keep only unique instances in a NSHashTable
and use pointer comparison instead of isEqual:
.
So my designated initializer looks like this:
- initWithStuff: (NSString *)stuff;
{
self = [super init];
if (!self) return nil;
// ... (actual initialisation code omitted)
hash = UniquingHashTable();
static OSSpinLock spinlock = OS_SPINLOCK_INIT;
OSSpinLockLock( &spinlock );
id member = [hash member: self];
if (member) self = member;
else [hash addObject: self];
OSSpinLockUnlock( &spinlock );
return self;
}
UniquingHashTable()
returns the global NSHashTable
or creates it via [NSHashTable weakObjectsHashTable]
if it doesn’t exist yet. The weakObjectsHashTable
is the important bit - it stores weak pointers to the objects that automatically get removed once there is no other strong reference to that object. The initialization is thread-safe thanks to dispatch_once
.
This works fine, peak memory usage is significantly lower and all my test cases still pass. But I am not quite sure if I can rely on pointer-comparison to test for equality. Is there any case or race condition where I get two distinct instances at the same time (so the pointers are different) but they still compare equal by isEqual:
?
To clarify what I have/want:
Given
MyClass *a = [[MyClass alloc] initWithStuff: stringA];
MyClass *b = [[MyClass alloc] initWithStuff: stringB];
we always have
[a isEqual: b] == [stringA isEqual: stringB];
This is not changed with the new code.
What I am trying to achieve is that also
[a isEqual: b] == (a == b)
so that I can replace the isEqual:
with a faster pointer comparison. (Yes, I measured this, this will be significant).
For single threaded code or if I was using a NSMutableSet
instead of a weak NSHashTable
this always works out. I’m just not sure if there is any race condition so that I can have the case where
[a isEqual: b] && (a != b)
is true.