[I have read the Cocoa memory management rules, but still want to be certain, and would like to know if this is good form.]
My class has a mutable dictionary ivar:
NSMutableDictionary *m_Dict;
...
m_Dict = [NSMutableDictionary dictionaryWithCapacity:10];
[m_Dict retain];
At some point I'll add a mutable array to the dictionary:
NSMutableArray *array = [NSMutableArray arrayWithCapacity:100];
[m_Dict setObject:array forKey: #"myArray"];
At this point, I believe that the array object has been retained by the dictionary. Therefore I am not retaining the object prior to adding it to the dictionary. Is this correct?
Later, I will access the array, for read purposes:
NSMutableArray *array = [m_Dict objectForKey: #"myArray"];
Q1. What is being returned by objectForKey? Is it a pointer to the object being held in the dictionary? Or a pointer to a COPY of the object? (I am presuming simply a pointer to the existing object is being returned.)
Q2. What has happened here, memory management wise? I am presuming that 'array' points to an object that is still retained (by the owning dictionary object) and that I do not have to retain the object to work with it. Correct?
Thanks.
Q1) The same pointer to the Objective-C array that you added before is returned. No copy is made.
Q2) Correct. All items in the array are owned by the array. If something owns something else it keeps a retain count on it.
When you remove the object from the array the retain count is reduced and the object is (maybe) deallocated.
Related
I have an NSMutableDictionary with a dozen keys, each key refers to an NSString value. If I call [myDictionary valueForKey:#"abc"] I will get an NSString object.
Is this NSString copied?
As there is no #property declaration, what happens with this retrieval of the string? I assume I get a copy. Is it any different with a NSMutableString?
My purpose is that I have an array of NSDictionaries. I call valueForKey on the array to get all the #"abc" keys from the collection of dictionaries. Thus I now have an array of NSStrings.
I need to pass that array to an NSOperation on a background thread. Thus, the NSStrings need to be copies so that modifying the dictionaries later will not cause the ones passed to the NSOperation to get changed.
From the documentation of -[NSDictionary valueForKey:]:
If key does not start with “#”, invokes objectForKey:. If key does start with “#”, strips the “#” and invokes [super valueForKey:] with the rest of the key.
From the documentation of -[NSMutableDictionary setObject:forKey:]:
If aKey already exists in the dictionary, anObject takes its place.
valueForKey: does objectForKey: and does not copy the object. setObject:forKey: replaces the object, it does not modify the object. If you do
[mutableDictionary setObject:#"klm" forKey:#"abc"];
a = [mutableDictionary valueForKey:#"abc"];
[mutableDictionary setObject:#"xyz" forKey:#"abc"];
now a is the old value #"klm".
NSDictionary keys are copied when set. In the code dictionary[key] = value the key object is copied, and the dictionary retains the copy. The value object is not copied; the caller and the dictionary share a reference to the same object.
When retrieving keys or object, there is no copying. dictionary.allKeys returns an array of the key objects in the dictionary, not copies. Similarly, dictionary[key] returns a reference to the value for key in the dictionary.
And just so you know, the #property (copy) only applies when setting the value. ObjC getters never copy objects, unless it is specifically documented that way or you write your own so it does that.
Similarly, and to get to your question, key-value method use the same underlaying rules for that property. So -setValue:forKey:, -setValue:forKeyPath:, -valueForKey:, -valueForKeyPath: etc. will get or set the proper value as if you accessed the property directly. In other words, if the value is copied when the property is set, -setValue:forKeyPath: will copy the value. If an accessor does not copy the value, then -valueForKey: will not copy the value.
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.
When I insert a new object in a Core Data managed object context and shortly after that try to find this new object in the NSArrayController (which is connect with the managedObjectContext through binding), I can't find it. I do the creation and search in one method.
My question now. How long does it take for a new inserted object to show up in the NSArrayControllers arrangedObject array?
Update:
Here is the code for inserting and fetching the new objects
NSEntityDescription *entity = [[[self managedObjectModel] entitiesByName] objectForKey:#"EntityName"];
NSManagedObject *object = [[NSManagedObject alloc] initWithEntity:entity insertIntoManagedObjectContext:[self managedObjectContext]];
...
[[self managedObjectContext] processPendingChanges];
[arrayController fetch:nil];
NSArray* objects = [arrayController arrangedObjects]; //the new object is not present in the array
It's not a matter of "how long" but "at what point". There's enough of a distinction that it's important to study it. :-)
Usually array controllers are automatically updated (re-fetch their contents in this case) on the next run-loop but technically "at some future run loop". If you want them to update immediately after inserting something, send your MOC a -processPendingChanges then ask the array controller to -fetch:.
Among the first things you read in the Core Data documentation is that it's an advanced Cocoa topic whose prerequisite knowledge includes Key Value Binding and Key Value Observing. The missing bit of knowledge that led you to this question is found in understanding of KVC/KVO (and the Cocoa Bindings layer).
Just found a fix for this. I use the setSelectedObjects: method of the NSArrayController to select the object. Don't know why I didn't used this method anyway!
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.
I have a NSString and I want to write its value to a NSMutableString. Is this valid:
NSString *temp = [NSString stringWithString:#"test"];
NSMutableString *mutable = temp;
I ask because although this seems doable, I would think that this would assign both temp and mutable to the same address space. I have this question a lot when passing values into a method or returning values from a method. Sometimes I see other people do this or create the mutable string with stringWithString or initWithString. Thanks
You can use the mutableCopy method, which creates a mutable copy of the receiver and applies to any class which adopts the NSMutableCopying protocol (of which NSString is one of them):
NSString *temp = [NSString stringWithString:#"test"];
NSMutableString *mutable = [temp mutableCopy];
This will create a mutable copy of the string, as a new string instance. In this case it doesn't apply, as temp is an autoreleased string, but you would otherwise need to release the old string that you have made a copy of, if you no longer need it.
Since mutableCopy contains "copy", then you need to memory-manage the new string (you take ownership of it according to the Apple Object Ownership Policy).
The method that you have used simply assigns mutable as a pointer to the previously instantiated NSString.
One thing that seems to confuse a lot of people with Cocoa is the difference between variables and objects.
You have to keep in mind that when you declare a variable like NSMutableString *mutable, you are not creating a string. This is just a pointer variable of type NSMutableString*. Structurally, it's the same as any other pointer — and, as the term "pointer" implies, it just points to something that actually lives elsewhere. Because of this, you technically can assign it to point to any object. But you're not turning that object into a mutable string — you're just lying about what kind of object the variable points to. Once you try to send the object a message that only NSMutableString can respond to, the jig is up!
As Perspx said, if you have a string that you want to mutate, you can use the mutableCopy method to get — you guessed it — a mutable copy. There are also a lot of NSString methods that don't mutate the string, but let you get a new string with certain changes made (for example, stringByAppendingString:). You can go a pretty long way with those.