Cocoa's NSDictionary: why are keys copied? - cocoa

All objects used as keys in NS(Mutable)Dictionaries must support the NSCopying protocol, and those objects are copied when they're used in the dictionary.
I frequently want to use heavier weight objects as keys, simply to map one object to another. What I really mean when I do that is effectively:
[dictionary setObject:someObject forKey:[NSValue valueWithPointer:keyObject]];
("When I come back and hand you this same key object instance again, get me that same value out.")
...which is exactly what I end up doing to get around this design sometimes. (Yes, I know about NSMapTable in desktop Cocoa; but e.g. iPhone doesn't support this.)
But what I don't really get is why copying the key is necessary or desirable in the first place. What does it buy the implementation or caller?

The copy ensures that the values used as keys don't change "underhand" while being used as keys. Consider the example of a mutable string:
NSMutableString* key = ...
NSMutableDictionary* dict = [[NSMutableDictionary alloc] init];
[dict setObject: ... forKey: key];
Let's assume that the dictionary did not copy the key, but instead just retained it. If now, at some later point, the original string is modified, then it is very likely that you are not going to find your stored value in the dictionary again even if you use the very same key object (i.e., the one key points to in the example above).
In order to protect yourself against such a mistake, the dictionary copies all keys.
Note, by the way, that it is simple enough to define -copyWithZone: as just doing return [self retain]. This is allowed and good code if your object is immutable, and the NSCopying contract is specifically designed such that the object returned has to be (sorta, kinda) immutable:
Implement NSCopying by retaining the original instead of creating a new copy when the class and its contents are immutable.
(from NSCopying Reference)
and
The copy returned is immutable if the consideration “immutable vs. mutable” applies to the receiving object; otherwise the exact nature of the copy is determined by the class.
(from -copyWithZone: Reference)
Even if your objects are not immutable, you might get away with that implementation if you only ever use identity-based equality/hash implementations, i.e., implementations which are not affected in any way by the object's internal state.

If you want to store pointers as keys then you'll need to wrap them in a NSValue object with +valueWithPointer:.

Since iOS 6 if you want to use pointers as keys, you can use the NSMapTable object, see http://nshipster.com/nshashtable-and-nsmaptable/
You can specify whether keys and/or values are stongly or weakly held:
NSMapTable *mapTable = [NSMapTable mapTableWithKeyOptions:NSMapTableStrongMemory
valueOptions:NSMapTableWeakMemory];
Another option that could be appropriate sometimes is to use NSCache, which holds keys strongly and is actually thread-safe.

Related

Why use initWith.. methods as opposed to "class/factory methods" in Objective-C?

I've been coding in Objective-C for a few months now and I've noticed that sometimes a class is instantiated (as recommended by documentation) with an init method. Therefore, one must alloc first, and then init. [[Example Class Alloc] initWithProperty1:andTwo:]. However, sometimes the doc recommends using "factory methods" as constructors. Such as [NSArray arrayWithObjects:__].
It seems that with a factory/class method you get the allocation done behind the scenes and the actual method is indistinguishable from the init, AFAIK.
Therefore, what is the practical reason to prefer one over the other? Is my analysis of the two being nearly identical even correct?
I recommend using factory method if there is one that does what you need. Aside from the syntactic sugar (shorter), there are also differences in the object's ownership (and hence who should free it). You shouldn't worry so much about memory if you use ARC.
From Apple's documentation:
Factory methods can be more than a simple convenience. They can not
only combine allocation and initialization, but the allocation can
inform the initialization. As an example, let’s say you must
initialize a collection object from a property-list file that encodes
any number of elements for the collection (NSString objects, NSData
objects, NSNumber objects, and so on). Before the factory method can
know how much memory to allocate for the collection, it must read the
file and parse the property list to determine how many elements there
are and what object type these elements are.
That is a little mystic but consider a use case like this: you want to populate an NSMutableArray with the content of a file. If you choose "alloc and init", the OS must constantly allocate new memory to store the additional data as you read them from the file. The class method needs to parse the file first, so it know how many lines there are and how big of a memory it should ask for in one go.

Memory Leak with NSMutableArray

I am trying to set up a multi-dimensional NSMutableArray. I am initially setting all position to a [NSNumber numberWithInt:0] then replacing the object with another [NSNumber numberWithInt:4] (for example). When I am done I would like to rebuild the array. I am correct in saying [array release]? Will that release all the NSNumber objects? Or do I need to do more advance memory management, like set all objects to nil first?
You can either release the array and recreate it or—slightly more efficiently—just call the array’s -removeAllObjects. The NSNumber objects you’re populating it with are autoreleased, so the array, by taking ownership of them when you add them to it, also assumes responsibility for releasing them when it itself gets released or has its contents removed.
Your array will properly retain and release your NSNumbers as you add/replace and remove objects, as well as when you release the array holding the items. So yes you are correct since you are using the NSNumbers convenience constructor which will return an autoreleased object.
Philosophically, you shouldn't know or care what the NSArray does with respect to retain and release. The extent of your contract with it is that addObject:/etc will put an object into the array and objectAtIndex:/etc will subsequently return the same objects. At most you need to consider whether you need to continue owning an object after putting it into an array, entirely according to your own requirements. NSArray is entirely responsible for its own memory management.
In the case of NSArray, how it manages retains and releases internally is well known and your literal question is already answered by Noah and Joe. But you should never, ever rely on another object having a specific implementation.

What is the KVC Search Pattern for mutableArrayValueForKey?

I'm attempting to understand Cocoa's Key-Value Coding (KVC) mechanism a little better. I've read Apple's Key-Value Programming Guide but am still a little confused about how certain KVC methods search for keys. Particularly, mutableArrayValueForKey:.
Below I'm going to explain how I understand valueForKey: KVC "getters" to work. Then I'll get to my question regarding mutableArrayValueForKey.
There are seven different "getter" KVC methods:
- (id)valueForKey:(NSString *)key;
- (id)valueForKeyPath:(NSString *)keyPath;
- (NSDictionary *)dictionaryWithValuesForKeys:(NSArray *)keys;
- (NSMutableArray *)mutableArrayValueForKey:(NSString *)key;
- (NSMutableArray *)mutableArrayValueForKeyPath:(NSString *)keyPath;
- (NSMutableSet *)mutableSetValueForKey:(NSString *)key;
- (NSMutableSet *)mutableSetValueForKeyPath:(NSString *)keyPath;
When searching for a Value inside a Property (named myKey), Apple's docs state that valueForKey: searches like this:
Tries -getMyKey, -myKey, and -isMyKey (in that order) inside the receiver
If not found, it attempts these ordered, to-many getters (NSArray):
// Required:
- (NSUInteger)countOfMyKey;
// Requires At Least One:
- (id)objectInMyKeyAtIndex:(NSUInteger)index;
- (NSArray *)myKeyAtIndexes:(NSIndexSet *)indexes;
// Optional (improves performance):
- (void)getMyKey:(KeyClass **)buffer range:(NSRange)inRange;
Next, it attempts these unordered, to-many getters (NSSet):
- (NSUInteger)countOfMyKey;
- (NSEnumerator *)enumeratorOfMyKey;
- (KeyClass *)memberOfMyKey:(KeyClass *)anObject;
Next, it attempts to access Instance Variables directly, assuming YES is returned by accessInstanceVariablesDirectly, in this order: _myKey, _isMyKey, myKey, isMyKey.
Lastly, it gives up and calls the receiving class's - (id)valueForUndefinedKey:(NSString *)key method. Usually an error is raised here.
My question is, what is the search order pattern for mutableArrayValueForKey:?
Apple's docs state this:
Accessor Search Pattern for Ordered
Collections
The default search pattern for
mutableArrayValueForKey: is as
follows:
The receiver's class is searched for a
pair of methods whose names match the
patterns -insertObject:inAtIndex:
and -removeObjectFromAtIndex:
(corresponding to the NSMutableArray
primitive methods
insertObject:atIndex: and
removeObjectAtIndex: respectively), or
methods matching the pattern
-insert:atIndexes: and -removeAtIndexes: (corresponding to the
NSMutableArrayinsertObjects:atIndexes:
and removeObjectsAtIndexes: methods).
If at least one insertion method and
at least one removal method are found
each NSMutableArray message sent to
the collection proxy object will
result in some combination of
-insertObject:inAtIndex:, -removeObjectFromAtIndex:, -insert:atIndexes:, and -removeAtIndexes: messages being sent to the original receiver of
mutableArrayValueForKey:.
...etc...
This makes no sense to me as it's discussing "setter" like methods. mutableArrayValueForKey: returns an NSMutableArray. All of the methods listed above return void, and are used to edit an NSMutableArray, not get it. Example:
- (void)insertMyKey:(KeyClass *)keyObject inMyKeyAtIndex:(NSUInteger)index;
- (void)removeObjectFromMyKeyAtIndex:(NSUInteger)index;
Any idea what Apple is trying to say in their docs, or if this is perhaps an error?
My theory is that mutableArrayValueForKey: is likely taking a similar path as valueForKey: when searching to retrieve a KVC value. I'm just not sure what path that really is.
Thanks for any help you can offer! :)
The NSMutableArray you get back from calling mutableArrayValueForKey: is actually a private subclass of NSMutableArray which overrides normal array methods such as -count, -objectAtIndex:, -insertObject:atIndex:, etc. and calls the corresponding KVC methods on the object the array was retrieved from. It basically acts as a proxy for manipulating the to-many relationship of the object, and it's not something you have to worry about creating or returning yourself. A quick example of usage:
Playlist* aPlaylist;
Track* aTrack;
NSMutableArray* mutableTracks = [aPlaylist mutableArrayValueForKey:#"tracks"];
[mutableTracks insertObject:aTrack atIndex:0];
This piece of code adds a track to the beginning of the playlist. If the Playlist class implements KVC methods for its "tracks" relationship, then calling a method on the mutable array will result in the appropriate method being called on the underlying object. So in this example, when you call insertObject:atIndex: on the array, the array will in turn call insertObjectInTracks:atIndex: on the playlist object, and the track gets added to the playlist's array of tracks.
Now, in this example, of course you could just call insertObjectInTracks:atIndex: directly, but there are several advantages you can get out of using mutableArrayValueForKey: instead.
The array wrapper hides the implementation details of the underlying KVC methods. Implementing the entire list of methods isn't strictly required to be KVC compliant. The Playlist class could just implement -tracks and -setTracks:, and the code above will still work. In this case, instead of calling insertObjectInTracks:atIndex:, the mutable array proxy will create a new array with the object inserted at the beginning, and then just call setTracks: on the Playlist object. This is obviously less efficient, so implementing the full list of KVC methods is usually recommended.
In the case where, instead of a constant string for the key, you instead have a variable, using mutableArrayValueForKey: allows you to manipulate the relationship without having to know the exact names of the methods you have to call. As long as the object is KVC compliant for the key you're using, everything will "just work".
It also lets you use any method that NSMutableArray itself implements, so for example you could use methods that search the array for objects, sort the array, etc. without having to rewrite special versions to deal with the KVC stuff.

How to find a string in an NSArray?

This feels like such a stupid question, but how can I find a string in an NSArray?
I tried using
[array indexOfObjectIdenticalTo:myString]
but that requires the sting to have the same address.
Does anyone have any tips on how to do this?
You want the indexOfObject: method, which looks for the object by sending each object in the array an isEqual: message.
Peter's answer is correct.
One additional note; if you have tons and tons of strings in the array, -indexOfObject: is going to do a linear search. This may prove to be a performance bottleneck for which you should consider using a different container; an NSSet or NSDictionary, possibly (depending on what the strings mean).
Another gotcha is if the strings are all relatively similar and/or relatively long.
Of course, don't bother optimizing anything until you have used the analysis tools to prove that you have a performance issue.
You can use NSOrderSet as the container, the over view in NSOrderedSet Class Reference is below:
NSOrderedSet and its subclass, NSMutableOrderedSet, declare the programmatic interfaces to an ordered collection of objects.
NSOrderedSet declares the programmatic interface for static sets of distinct objects. You >establish a static set’s entries when it’s created, and thereafter the entries can’t be >modified. NSMutableOrderedSet, on the other hand, declares a programmatic interface for >dynamic sets of distinct objects. A dynamic—or mutable—set allows the addition and deletion >of entries at any time, automatically allocating memory as needed.
You can use ordered sets as an alternative to arrays when the order of elements is important >and performance in testing whether an object is contained in the set is a consideration— >testing for membership of an array is slower than testing for membership of a set.
Visit http://developer.apple.com/library/mac/#documentation/Foundation/Reference/NSOrderedSet_Class/Reference/Reference.html
containsObject:
Returns a Boolean value that indicates whether a given object is present in the array.
(BOOL)containsObject:(id)anObject
Parameters
anObject
An object.
Return Value
YES if anObject is present in the array, otherwise NO.
Discussion
This method determines whether anObject is present in the array by sending an isEqual: message to each of the array’s objects (and passing anObject as the parameter to each isEqual: message).
Declared In
NSArray.h

NSManagedObject as NSDictionary key?

In my app, I have a NSDictionary whose keys should be instances of a subclass of NSManagedObject.
The problem, however, is that NSManagedObject does not implement the NSCopying protocol which means that no Core Data objects / instances of NSManagedObject can be used as dictionary keys even though the -[hash] method works fine for them.
Was should I do?
There are four options:
Use a different object as the dictionary key instead, and lookup from that. [object objectID] or +[NSValue valueWithNonretainedObject:] seem the most obvious
Use CFDictionaryCreateMutable() to create a dictionary with retained keys, rather than copied, instead, and then call CFDictionarySetValue() to store the objects
On OS X or iOS6+, [NSMapTable mapTableWithStrongToStrongObjects] gives you a purely Objective-C equivalent to CFMutableDictionary
Implement NSCopying for your managed object subclass, such that it returns self (with a bumped reference count if you're not using ARC)
Notes
+valueWithNonretainedObject: is pretty dangerous, since it's possible to be left with a dangling pointer; likely best to avoid.
Storing object IDs is fine, apart from the fact that new objects start out life with a temporary ID. That ID then changes to a permanent one when the context is saved to disk (or -obtainPermanentIDsForObjects:… is called). Your mapping code needs to be smart enough to handle this unless it can guarantee that all incoming objects already have a permanent ID.
Implementing NSCopying like this feels a bit icky, but should work just fine. As it happens, this is exactly the approach NSURLSessionTask takes, I presume for dictionary friendliness.
Prior to OS X 10.8 Mountain Lion, it used to be possible to create a regular NSMutableDictionary and then call CFDictionarySetValue() for it. That's no longer the case though; new dictionaries now have proper copy callbacks specified down at the CF level, rather than purely being a feature of NSMutableDictionary.
I suggest to use [[[myManagedObject objectID] URIRepresentation] absoluteString] as your key.
Could you create a wrapper class, that contains a reference to the instance of NSManagedObject that you want to use as a dictionary key? You could then make this wrapper class implement NSCopying, along with a hash method (perhaps just calling the NSManagedObject's hash method), and use this wrapper as the dictionary key.
I had a similar problem, in which I needed to bundle several entities with additional data for each, and initially tried:
#{entity1:data1, #entity2:data2, #entity3:data3}
this didn't work for the reason above (NSCopying), so I did:
#[
#{#"entity":entity1, #"data":data1},
#{#"entity":entity2, #"data":data2},
#{#"entity":entity3, #"data":data3}
]
But this solution makes sense only if you don't need dictionary style access to these entities or are happy to iterate to find what you need. In my case this was a packaging problem. Note that if you pass these entities around the NSManagedObjectContext need to be the same to use them.

Resources