4

I have a custom class I'm using instances of as keys in a dictionary. The problem is, sometimes (not all the time) requesting a value from a dictionary with one of my custom instance keys returns nil when the key really is in the dictionary. I looked around the web on how to use custom objects as dictionary keys, and all I've been able to find is that you need to implement the NSCopying protocol, which I have done.

My class looks like this:

// .h
@interface MyObject: NSObject <NSCopying>

@property (copy) NSString* someString;
@property (copy) NSString* otherString;
@property int someInt;

+ (instancetype) objectWithString:(NSString*)str;
- (id) copyWithZone:(NSZone*)zone;
@end

// .m
@implementation MyObject

@synthesize someString;
@synthesize otherString;
@synthesize someInt;

+ (instancetype) objectWithString:(NSString*)str {
   id obj = [[self alloc] init];
   [obj setSomeString:str];
   [obj setOtherString:[str lowercaseString];
   [obj setSomeInt:0];
   return obj;
}

- (id) copyWithZone:(NSZone*)zone {
   id other = [[[self class] allocWithZone:zone] init];
   [other setSomeString:self.someString];
   [other setOtherString:self.otherString];
   [other setSomeInt:self.someInt];
   return other;
}
@end

Then I put some of these things in a mutable dictionary like this:

MyObject* key1 = [MyObject objectWithString:@"Foo"];
MyObject* key2 = [MyObject objectWithString:@"Bar"];
NSNumber* value1 = [NSNumber numberWithInt:123];
NSNumber* value2 = [NSNumber numberWithInt:456];

NSMutableDictionary* theDict = [[NSMutableDictionary alloc] init];
[theDict setObject:value1 forKey:key1];
[theDict setObject:value2 forKey:key2];

Now what I want to do is just pop a key/value pair out of the dictionary, so I do that like this:

MyObject* theKey = [[theDict allKeys] firstObject];
NSNumber* theValue = [theDict objectForKey:theKey];

And here's where I run into problems. Most of the time, this works fine. However, sometimes objectForKey:theKey returns nil. I've put a breakpoint in my code and can confirm that theKey is indeed in the theDict.

What am I doing wrong here?

joxl
  • 287
  • 1
  • 4
  • 9
  • You don't use a *custom class* as a key in a dictionary. You use an *instance* of a custom class as a key. That's something else and changes the meaning of your question. – Nikolai Ruhe Jan 13 '14 at 16:50
  • 2
    I would guess that you did not implement the isEqual and hash methods correctly. Why don't you NSLog the values they're returning. – Hot Licks Jan 13 '14 at 16:55
  • Thanks @NikolaiRuhe, I edited the question to clarify this (that I'm using *instances*). – joxl Jan 13 '14 at 17:11

1 Answers1

6

You should also implement -isEqual: and -hash on your custom objects.

For more info: Apple Documentation, NSHipster article

The reason you must do this is the keys can be copied when you put them in the dictionary and the default implementation of isEqual just does pointer comparison.

Lance
  • 8,872
  • 2
  • 36
  • 47
  • This is good advice, but I'm not sure it is the issue in this case. Unless `-allKeys` copies the keys, he is looking for an object that is one of the keys. – JeremyP Jan 13 '14 at 16:53
  • They keys can and will be copied in some cases. That's why keys must conform to `NSCopying` – Lance Jan 13 '14 at 16:54
  • They are copied when you add them to the dictionary, but if you look at his code, he is getting the list of keys out of the dictionary and using one of them in `-objectForKey:`, so he is using the object that is the copy. – JeremyP Jan 13 '14 at 16:55
  • from the docs: `The key is copied (using copyWithZone:; keys must conform to the NSCopying protocol).` – Lance Jan 13 '14 at 16:55
  • oh I see what you mean. Regardless, whether or not `allKeys` copies the keys or not is an implementation detail. Best practice dictates the key implement the equality methods described. – Lance Jan 13 '14 at 16:57
  • I agree, I just don't think it is the cause of the problem (at least, not as described by the question). – JeremyP Jan 13 '14 at 17:00
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/45137/discussion-between-lance-and-jeremyp) – Lance Jan 13 '14 at 17:05
  • The [Hipster article](http://nshipster.com/equality/) was very helpful in aiding my implementation, thanks. – joxl Jan 13 '14 at 17:14