0

I am very new to Objective-C and I have come across the NSMutableDictionary. I want to insert an object into the key and from my understanding, you need to make a copy by using the NSCopying class.

I have been reading the Apple's Documentations and I am still puzzled with this. Also I have been searching for examples and I can only seem to find ones that have keys as NSString objects which seems to be automatically copied.

Here is the part of my implementation code:

@implementation League
- (void) setPlayerInTeam: (Club*) theClub playerObject: (Person*) person{
    [playerTeam setObject:theClub forKey: person];
}
@end

the forKey:person is obviously wrong, how do I make a copy of this by using the NSCopying? Sorry for being a newbie but I am eager to learn.

Many thanks.

jonprasetyo
  • 3,356
  • 3
  • 32
  • 48
  • You're getting a bit confused by the documentation. An object used as a key must support the [NSCopying protocol](https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Protocols/NSCopying_Protocol/Reference/Reference.html#//apple_ref/occ/intf/NSCopying), which is to say its class must implement `copyWithZone`. – Hot Licks Jan 01 '14 at 14:23
  • So do you mean my class League must have the - (id) copyWithZone:(NSZone *)zone{ } ? – jonprasetyo Jan 01 '14 at 14:39
  • Or in the Person class? – jonprasetyo Jan 01 '14 at 14:40
  • The class of your `person` variable must support `copyWithZone`. (And it must actually work.) And the `isEqual` and `hash` methods must also produce valid results. – Hot Licks Jan 01 '14 at 14:44
  • (It's often simpler/more efficient to use a NSString ID of some sort for the key and, if necessary. keep a separate ID-to-person dictionary somewhere else.) – Hot Licks Jan 01 '14 at 14:47
  • Thanks for your reply. I see it seems to be much more simpler to do it the NSString id. However, I am just wondering, is there a necessary case to use objects as keys? – jonprasetyo Jan 01 '14 at 14:54
  • Sometimes you want to use the dictionary to help eliminate duplicates. Eg, you'd consider to be equal a person with the same name and address. In such a case the more complex approach is useful. – Hot Licks Jan 01 '14 at 15:41
  • Why not just make a `club` property on `Person` and set that to the person's club? – Aaron Brager Jan 01 '14 at 16:13
  • @AaronBrager - Because the Person might be a member of several Clubs. – Hot Licks Jan 01 '14 at 20:05
  • Okay, why not just make a `clubs` property on `Person`? It could be an array or set of clubs. Also, if the Person is a member of several Clubs, then the person cannot be used as the key for the dictionary. – Aaron Brager Jan 01 '14 at 21:14

2 Answers2

1

The keys you use in an NSMutableDictionary have certain restrictions. NSMutableDictionary copies the key when you use -setObject:forKey:. This means the key must support the NSCopying protocol. So Person needs to be declared like this:

@interface Person : PersonsSuperClass <NSCopying>

and it needs to implement the method -copyWithZone: If the Person class is immutable and you are using ARC, -copyWithZone: can simply return self.

-(id) copyWithZone: (NSZone*) zone
{
    return self;  // or return [self retain] if not using ARC.
}

If Person is not immutable, -copyWithZone: needs to make a new Person object that is exactly the same as the one you are copying.

-(id) copyWithZone: (NSZone*) zone
{
    Person* theCopy = [[[self class] allocWithZone: zone] init];
    // copy all the data from self to theCopy
    return theCopy;
}

There are some other things you need to be careful of too. The method -isEqual: must be semantically correct because that is how comparisons are done by NSMutableDictionary. For example, if a Person is uniquely indexed by a property called userId, you need to make -isEqual: use that property to determine if two Person object are equal. A similar rule exists for -hash, two objects that compare equal using -isEqual: must have the same hash.

JeremyP
  • 84,577
  • 15
  • 123
  • 161
  • `[[[self class] allocWithZone:zone] init]` is preferable to `[[Person alloc] init]`. Subclasses of `Person` would not get allocated correctly. In addition, zones might be used eventually in iOS so the zone semantics should be used. – Jeffery Thomas Jan 01 '14 at 15:12
  • Thanks for your answer, will read further on the equals and hash. – jonprasetyo Jan 01 '14 at 15:28
  • @JefferyThomas Good spot. I'll fix it. – JeremyP Jan 01 '14 at 15:52
0

As Hot Licks said, you must create a -[Person copyWithZone:] method.

@interface Person : NSObject <NSCopying>
@property NSString *firstName;
@property NSString *lastName;
…
@end

@implementation
- (id)copyWithZone:(NSZone *)zone
{
    Person *copy = [[[self class] allocWithZone:zone] init];
    if (copy) {
        copy->_firstName = [self.firstName copyWithZone:zone];
        copy->_lastName = [self.lastName copyWithZone:zone];
        …
    }
    return copy;
}
@end

It's a real pain and full of possible subtle defects. In addition, I would highly recommend creating -[Person isEqual:] and -[Person hash] methods to avoid other types of defects.

Sometimes it's a must, but avoid it when possible.

Community
  • 1
  • 1
Jeffery Thomas
  • 42,202
  • 8
  • 92
  • 117