Memory Leak with NSMutableArray - cocoa

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.

Related

Cocoa - NSDictionary objectForKey - memory management clarification

[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.

NSArrayController and KVO

What do I need to do to update a tableView bound to an NSArrayController when a method is called that updates the underlying array? An example might clarify this.
When my application launches, it creates a SubwayTrain. When SubwayTrain is initialised, it creates a single SubwayCar. SubwayCar has a mutable array 'passengers'. When a Subway car is initialised, the passengers array is created, and a couple of People objects are put in (let's say a person with name "ticket collector" and another, named "homeless guy"). These guys are always on the SubwayCar so I create them at initialisation and add them to the passengers array.
During the life of the application people board the car. 'addPassenger' is called on the SubwayCar, with the person passed in as an argument.
I have an NSArrayController bound to subwayTrain.subwayCar.passengers, and at launch my ticket collector and homeless guy show up fine. But when I use [subwayCar addPassenger:], the tableView doesn't update. I have confirmed that the passenger is definitely added to the array, but nothing gets updated in the gui.
What am I likely to be doing wrong? My instinct is that it's KVO related - the array controller doesn't know to update when addPassenger is called (even though addPassenger calls [passengers addObject:]. What could I be getting wrong here - I can post code if it helps.
Thanks to anyone willing to help out.
UPDATE
So, it turns out I can get this to work by changing by addPassenger method from
[seatedPlayers addObject:person];
to
NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];
I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it? It seems awfully cumbersome to copy the array, release the old one, and update the copy (as opposed to just adding to the existing array).
I don't know if its considered a bug, but addObject: (and removeObject:atIndex:) don't generate KVO notifications, which is why the array controller/table view isn't getting updated. To be KVO-compliant, use mutableArrayValueForKey:
Example:
[[self mutableArrayValueForKey:#"seatedPlayers"] addObject:person];
You'll also want to implement insertObject:inSeatedPlayersAtIndex: since the default KVO methods are really slow (they create a whole new array, add the object to that array, and set the original array to the new array -- very inefficient)
- (void)insertObject:(id)object inSeatedPlayerAtIndex:(int)index
{
[seatedPlayers insertObject:object atIndex:index];
}
Note that this method will also be called when the array controller adds objects, so its also a nice hook for thinks like registering an undo operation, etc.
I haven't tried this, so I cannot say it works, but wouldn't you get KVO notifications by calling
insertObject:atArrangedObjectIndex:
on the ArrayController?
So, it turns out I can get this to work by changing by addPassenger method from
[seatedPlayers addObject:person];
to
NSMutableSet *newSeatedPlayers = [NSMutableSet setWithSet:seatedPlayers];
[newSeatedPlayers addObject:sp];
[seatedPlayers release];
[self setSeatedPlayers:newSeatedPlayers];
I guess this is because I am using [self setSeatedPlayers]. Is this the right way to do it?
First off, it's setSeatedPlayers:, with the colon. That's vitally important in Objective-C.
Using your own setters is the correct way to do it, but you're using the incorrect correct way. It works, but you're still writing more code than you need to.
What you should do is implement set accessors, such as addSeatedPlayersObject:. Then, send yourself that message. This makes adding people a short one-liner:
[self addSeatedPlayersObject:person];
And as long as you follow the KVC-compliant accessor formats, you will get KVO notifications for free, just as you do with setSeatedPlayers:.
The advantages of this over setSeatedPlayers: are:
Your code to mutate the set will be shorter.
Because it's shorter, it will be cleaner.
Using specific set-mutation accessors provides the possibility of specific set-mutation KVO notifications, instead of general the-whole-dang-set-changed notifications.
I also prefer this solution over mutableSetValueForKey:, both for brevity and because it's so easy to misspell the key in that string literal. (Uli Kusterer has a macro to cause a warning when that happens, which is useful when you really do need to talk to KVC or KVO itself.)
The key to the magic of Key Value Observing is in Key Value Compliance. You initially were using a method name addObject: which is only associated with the "unordered accessor pattern" and your property was an indexed property (NSMutableArray). When you changed your property to an unordered property (NSMutableSet) it worked. Consider NSArray or NSMutableArray to be indexed properties and NSSet or NSMutableSet to be unordered properties. You really have to read this section carefully to know what is required to make the magic happen... Key-Value-Compliance. There are some 'Required' methods for the different categories even if you don't plan to use them.
Use willChangeValueForKey: and didChangeValueForKey: wrapped around a change of a member when the change does not appear to cause a KVO notification. This comes in handy when you are directly changing an instance variable.
Use willChangeValueForKey:withSetMutation:usingObjects: and didChangeValueForKey:withSetMutation:usingObjects: wrapped around a change of contents of a collection when the change does not appear to cause a KVO notification.
Use [seatedPlayers setByAddingObject:sp] to make things shorter and to avoid needlessly allocating mutable set.
Overall, I'd do either this:
[self willChangeValueForKey:#"seatedPlayers"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:sp];
[seatedPlayers addObject:sp];
[self didChangeValueForKey:#"seatedPlayers"
withSetMutation:NSKeyValueUnionSetMutation
usingObjects:sp];
or this:
[self setSeatedPlayers:[seatedPlayers setByAddingObject:sp]];
with the latter alternative causing an automatic invocation of the functions listed under 1. First alternative should be better performing.

Cocoa's NSDictionary: why are keys copied?

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.

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