An alternative to using NSMapTable (which is unavailable on the iPhone, for instance) is to use the CoreFoundation equivalents. Those can contain regular non-retained pointers. You still need to pull things out of that list however, but that can be done easily in an object's -dealloc method.
One possibility is using a CFSet. This would require that your objects all respond to -isEqual:
and -hash
properly, so that the set can determine if an object is already present accurately.
For instance (note that I've not compiled this but it ought to be at least 90% correct):
static id __uniqueObjects = nil; // GC uses NSHashTable, non-GC uses NSSet (inited using CFSet APIs)
@interface MyHost : NSObject
{
NSURL * _hostURL;
}
@property (nonatomic, readonly) NSURL * hostURL;
+ (MyHost *) uniqueHost: (MyHost *) aHost;
+ (void) removeUniqueHost: (MyHost *) aHost; // only used in non-GC environment
- (id) initWithURL: (NSURL *) hostURL;
@end
@implementation MyHost
- (id) initWithURL: (NSURL *) hostURL
{
self = [super init];
if ( self == nil )
return ( nil );
// use copy, because NSURL implements NSCopying protocol
_hostURL = [hostURL copy];
// if there's a version around already, release this instance
MyHost * result = [[self class] uniqueHost: self];
if ( result != self )
{
// set _hostURL to nil so we don't inadvertently remove anything from uniqueing table in -dealloc
[_hostURL release];
_hostURL = nil;
[self release];
}
// return either self or a pre-existing unique instance
return ( result );
}
- (void) dealloc
{
// non-GC objects need to explicitly remove themselves from the uniqueing table
[[self class] removeUniqueHost: self];
[_hostURL release];
[super dealloc];
}
// no need for -finalize -- under GC we use NSHashTable with weak references
- (NSUInteger) hash
{
return ( [_hostURL hash] );
}
- (BOOL) isEqual: (MyHost *) other
{
if ( other == self )
return ( YES );
return ( [_hostURL isEqual: other->_hostURL] );
}
+ (MyHost *) uniqueHost: (MyHost *) aHost
{
if ( __uniqueObjects == nil )
{
// use low-level routine to check, because iPhone has no NSGarbageCollector class
// we use NSHashTable or NSMutableSet, because they both respond to the same messages (NSHashTable is modeled on NSMutableSet)
if ( objc_collectingEnabled() )
{
// hash table has zeroing weak memory, object equality (uses -isEqual:), and does NOT copy objects, just retains them
__uniqueObjects = [[NSHashTable hashTableWithWeakObjects] retain];
}
else
{
CFSetCallBacks cb = {
0, // version
NULL, // retain (non-retaining references)
NULL, // release (non-retaining references)
CFCopyDescription, // CF (plain C function) equivalent for -description
CFEqual, // CF (plain C function) equivalent for -isEqual:
CFHash // CF (plain C function) equivalent for -hash
}
__uniqueObjects = (NSMutableSet *) CFSetCreateMutable( kCFAllocatorDefault, 0, &cb );
}
}
MyHost * result = nil;
@synchronized(__uniqueObjects)
{
// treat both like an NSMutableSet & we're golden
// we use the -member: function to get an existing matching object from the collection
result = [[__uniqueObjects member: aHost] retain];
if ( result == nil )
{
// store & return the passed-in host object
[__uniqueObjects addObject: aHost];
result = aHost;
}
}
return ( result );
}
+ (void) removeUniqueHost: (MyHost *) aHost
{
if ( __uniqueObjects == nil )
return; // shouldn't ever happen, but it's a good idea to check at least
@synchronized(__uniqueObjects)
{
[__uniqueObjects removeObject: aHost];
}
}
@end
Hopefully I've put enough in here to answer any questions/thoughts you might have about this design pattern.