5

I'm using NSMutableDictionary to store what is effectively a descriptor for certain classes, because I'd rather not waste the memory of adding the descriptor to every instance of classes, since only a very small subset of 1000s of objects will have the descriptor.

Unfortunately, given:

MyClass* p = [MyClass thingy];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
NSString* description = @"blah"; //does this work? If not I'm just simplifying for this example.
[dict setObject:description forKey:p]; // BZZZZT a copy of p is made using NSCopying

MyClass* found = [dict objectForKey:p]; //returns nil, as p becomes a different copy.

So that doesn't work.

I can hack it by passing in an NSNumber like so:

[dict setObject:description forKey:[NSNumber numberWithInt:(int)p]]; // this is cool

But that's not only ugly, but error prone since it is nonstandard.

With that in mind, is there a clean way to do this?

Charles Randall
  • 6,920
  • 12
  • 33
  • 38

5 Answers5

7

NSValue confirms NSCopying as per NSValue Class Reference. So you can use an instance of NSValue as a key.

Use +valueWithPointer: to wrap the pointer value up.

NSString* foo = @"foo";
id bar = @"bar";

NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
[dict setObject:@"forKey:foo" forKey:foo];
[dict setObject:@"forKey:bar" forKey:bar];
[dict setObject:@"forKey:[NSValue... foo]" forKey:[NSValue valueWithPointer:foo]];
[dict setObject:@"forKey:[NSValue... bar]" forKey:[NSValue valueWithPointer:bar]];

NSLog(@"%@", [dict objectForKey:foo]); 
NSLog(@"%@", [dict objectForKey:bar]); 
NSLog(@"%@", [dict objectForKey:[NSValue valueWithPointer:foo]]); 
NSLog(@"%@", [dict objectForKey:[NSValue valueWithPointer:bar]]); 

Gives

2013-01-24 04:42:14.051 a.out[67640:707] forKey:foo
2013-01-24 04:42:14.052 a.out[67640:707] forKey:bar
2013-01-24 04:42:14.053 a.out[67640:707] forKey:[NSValue... foo]
2013-01-24 04:42:14.053 a.out[67640:707] forKey:[NSValue... bar]
xenosoz
  • 904
  • 7
  • 9
2

Your problem is that NSDictionary copies its keys. Either make your class implement NSCopying or use a CFMutableDictionary with a key callback that doesn't copy (it's toll-free bridged with NSMutableDictionary, so you can use it exactly the same after it's created).

Chuck
  • 234,037
  • 30
  • 302
  • 389
1

You may find it easier to use objc_setAssociatedObject/objc_getAssociatedObject. They're described here.

Nicholas Riley
  • 43,532
  • 6
  • 101
  • 124
  • I would rather stick within the constraints of how the class would normally function, or simply not use it at all. I don't want my API to use an NSDictionary that has to be used in a completely nonstandard fashion to function. – Charles Randall Apr 27 '11 at 12:58
  • Are you responding to the wrong answer? My suggestion doesn't involve NSDictionary at all. – Nicholas Riley Apr 27 '11 at 14:17
  • Oops, sorry, I skimmed the first time and misunderstood. This, while an improvement, doesn't help me because I want the ability to store the object based on context, since multiple pieces of code can potentially refer to the same object, and I don't want the 100% association, as I want the other code to be able to see if an object is associated, rather than it being in lockstep. I'm not sure I explained this well. – Charles Randall Apr 27 '11 at 21:29
0

As this is a problem that can occur often, I have created the following class to associate objects to other objects without copying of the key. Identity is determined on the key object address. It's a subclass of NSMutableDictionary, so all methods of that class are available.

ObjectMap.h

//
//  ObjectMap.h
//
//  Created by René Dekker on 07/03/2012.
//  Copyright (c) 2012 Renevision.
//

#import <Foundation/Foundation.h>

@interface ObjectMap : NSMutableDictionary

+ (ObjectMap *) objectMap;

- (bool) containsObject:(id)aKey;

// use the following instead of setObject:forKey: in IOS 6, to avoid warnings about NSCopying
- (void) setObject:(id)anObject forObjectKey:(id)aKey;


@end

ObjectMap.m

//
//  ObjectMap.m
//
//  Created by René Dekker on 07/03/2012.
//  Copyright (c) 2012 Renevision.
//

#import "ObjectMap.h"
#import <CoreFoundation/CoreFoundation.h>

@interface ObjectMapEnumerator : NSEnumerator 

@end

@implementation ObjectMapEnumerator {
    NSUInteger size;
    NSUInteger currentIndex;
    id *keysArray;
}

- (NSArray *) allObjects
{
    return [NSArray arrayWithObjects:keysArray count:size];
}

- (id) nextObject
{
    if (currentIndex >= size) {
        return nil;
    }
    return keysArray[currentIndex++];
}

- (id) initWithDict:(CFDictionaryRef)dict
{
    if (!(self = [super init])) {
        return nil;
    }
    size = CFDictionaryGetCount(dict);
    keysArray = malloc( size * sizeof(id) );
    currentIndex = 0;
    CFDictionaryGetKeysAndValues(dict, (const void **)keysArray, NULL);
    return self;
}

- (void) dealloc
{
    free(keysArray);
    [super dealloc];
}

@end

@implementation ObjectMap {
    CFMutableDictionaryRef theDictionary;
}

- (void) setObject:(id)anObject forKey:(id)aKey
{
    CFDictionarySetValue(theDictionary, aKey, anObject);
}

- (void) setObject:(id)anObject forObjectKey:(id)aKey
{
    CFDictionarySetValue(theDictionary, aKey, anObject);
}

- (id) objectForKey:(id)aKey
{
    return CFDictionaryGetValue(theDictionary, aKey);
}

- (void) removeObjectForKey:(id)aKey
{
    CFDictionaryRemoveValue(theDictionary, aKey);
}

- (void) removeAllObjects
{
    CFDictionaryRemoveAllValues(theDictionary);
}

- (bool) containsObject:(id)aKey
{
    return CFDictionaryContainsKey(theDictionary, aKey);
}

- (NSUInteger) count
{
    return CFDictionaryGetCount(theDictionary);
}

- (NSEnumerator *)keyEnumerator
{
    return [[[ObjectMapEnumerator alloc] initWithDict:theDictionary] autorelease];
}

#pragma - Object Life Cycle

static void dictionaryRelease(CFAllocatorRef allocator, const void* value) {
    if (0 != value) {
        CFRelease(value);
    }
}

static const void *dictionaryRetain(CFAllocatorRef allocator, const void* value) {
    if (0 != value) {
        return CFRetain(value);
    }
    return 0;
}

static CFDictionaryKeyCallBacks callbacks = { 0, &dictionaryRetain, &dictionaryRelease, &CFCopyDescription, NULL, NULL };

- (id) init
{
    if (!(self = [super init])) {
        return nil;
    }
    theDictionary = CFDictionaryCreateMutable(NULL, 0, &callbacks, &kCFTypeDictionaryValueCallBacks);
    return self;
}

- (void) dealloc
{
    CFRelease(theDictionary);
    [super dealloc];
}

+ (ObjectMap *) objectMap
{
    return [[[ObjectMap alloc] init] autorelease];
}

@end
fishinear
  • 6,101
  • 3
  • 36
  • 84
0

My personal solution:

override - (BOOL) isEqual:(id)object in your custom class.

in this method, compare each property inselfand object. if they are the same, return YES

I'm not sure how you implemented your - (id) copyWithZone:(NSZone *)zone method, but for my test project, it works fine with me.

attachment:

- (id) copyWithZone:(NSZone *)zone{
    testObject *copy = [[testObject allocWithZone:zone] init];
    NSString *tempCopyString = [NSString stringWithString:self.string]
    copy.string = tempCopyString;
    return copy;
}

- (BOOL) isEqual:(id)object{
    testObject *newObject = (testObject*)object;
    if ([newObject.string isEqualToString:object.string]) {
        return YES;
    }
    else {
        return NO;
    }
}
Xiao
  • 873
  • 6
  • 10
  • My class isn't lightweight, I don't want to create copies at all -- doing so would probably cost more than simply adding a pointer to every class that could possibly have a 'description'. – Charles Randall Apr 27 '11 at 12:56