Collection <CALayerArray: 0x1ed8faa0> was mutated while being enumerated - iphone

My app crashes some time while removing wait view from screen. Please guide me how can i improve code given below.
The wait view is only called when app is downloading something from server. and when it completed download then i call removeWaitView method.
Exception Type: NSGenericException
Reason: Collection was mutated while being enumerated.
+(void) removeWaitView:(UIView *) view{
NSLog(#"Shared->removeWaitView:");
UIView *temp=nil;
temp=[view viewWithTag:kWaitViewTag];
if (temp!=nil) {
[temp removeFromSuperview];
}
}
my waitview adding code is
+(void) showWaitViewInView:(UIView *)view withText:(NSString *)text{
NSLog(#"Shared->showWaitViewWithtag");
UIView *temp=nil;
temp=[view viewWithTag:kWaitViewTag];
if (temp!=nil)
{
return;
}
//width 110 height 40
WaitViewByIqbal *waitView=[[WaitViewByIqbal alloc] initWithFrame:CGRectMake(0,0,90,35)];
waitView.center=CGPointMake(view.frame.size.width/2,(view.frame.size. height/2) -15);
waitView.tag=kWaitViewTag; // waitView.waitLabel.text=text;
[view addSubview:waitView];
[waitView release];
}

The exception is pretty clear - a collection (in this case something like an array) is being modified while it is also being enumerated.
In this specific case we are talking about array of layers, or better said, instances of UIView which are all backed up by layers.
The modifications happen when you are calling removeFromSuperview or addSubview. The enumeration can happen anytime during redrawing.
My guess is - you are not calling your methods from the main thread and the main thread is currently redrawing, so you'll get a racing condition.
Solution: call these methods only from the main thread.

You should get a copy of the array, so you don't risk the chance of it being modified while your code is enumerating it. Of course this leaves the chance that a new item may be added as a result of the mutation, thus your enumeration will miss that one array item. Here is an example specific to the title of your question:
[[viewLayer.sublayers copy] enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
CALayer * subLayer = obj;
if(subLayer == theLayerToBeDeleted){
[subLayer removeFromSuperlayer];
}
}];

It's possible that you're adding or removing a the waitView while iterating through the waitView's siblings (it's superView's subviews). Check to see what methods call removeWaitView and showWaitInView to make sure the calling methods don't call the show/remove wait view methods from within a for loop iterating the wait view's siblings (the waitView's superview's subviews).

To fix issue just start from last element
NSMutableArray* Items = [[NSMutableArray alloc] initWithArray:[_ScrollList.documentView subviews]];
while ([Items count]>0) {
NSView *v = [Items lastObject];
[v removeFromSuperview];
[Items removeLastObject];
}

Related

Error when using NSMutableSet

I get the error
* Terminating app due to uncaught exception 'NSGenericException', reason: '* Collection <__NSCFSet: 0x6b66390> was mutated while being enumerated.'
when adding an new delegate to my class. Or at least, that's where I think the problem is.
This is my code: MyAppAPI.m
[...]
static NSMutableSet *_delegates = nil;
#implementation MyAppAPI
+ (void)initialize
{
if (self == [MyAppAPI class]) {
_delegates = [[NSMutableSet alloc] init];
}
}
+ (void)addDelegate:(id)delegate
{
[_delegates addObject:delegate];
}
+ (void)removeDelegate:(id)delegate
{
[_delegates removeObject:delegate];
}
[...]
#end
MyAppAPI is a singleton which I can use throughout my application. Wherever I can (or should be able to) do: [MyAppAPI addDelegate:self].
This works great, but only in the first view. This view has a UIScrollView with PageViewController which loads new views within itself. These new views register to MyAppAPI to listen to messages until they are unloaded (which in that case they do a removeDelegate).
However, it seems to me that it dies directly after I did a addDelegate on the second view in the UIScrollView.
How could I improve the code so that this doesn't happen?
Update
I'd like to clarify me a bit further.
What happens is that view controller "StartPage" has an UIScrollView with a page controller. It loads several other views (1 ahead of the current visible screen).
Each view is an instans PageViewController, which registers itself using the addDelegate function shown above to the global singleton called MyAppAPI.
However, as I understand this viewcontroller 1 is still reading from the delegate when viewcontroller 2 registers itself, hence the error shows above.
I hope I made the scenario clear. I have tried a few things but nothing helps.
I need to register to the delegate using addDelegate even while reading from the delegates. How do I do that?
Update 2
This is one of the reponder methods:
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
for (id delegate in _delegates)
{
if ([delegate respondsToSelector:#selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
Scott Hunter is right. This error is thrown when you try to edit a list while iterating.
So here is an example of what you may be doing.
+ (void)iteratingToRemove:(NSArray*)items {
for (id delegate in _delegates) {
if(delegate.removeMePlease) {
[MyAppAPI removeDelegate:delegate]; //error you are editing an NSSet while enumerating
}
}
}
And here is how you should handle this correctly:
+ (void)iteratingToRemove:(NSArray*)items
{
NSMutableArray *delegatesToRemove = [[NSMutableArray alloc] init];
for (id delegate in _delegates) {
if(delegate.removeMePlease) {
[delegatesToRemove addObject:delegate];
}
}
for(id delegate in delegatesToRemove) {
[MyAppAPI removeDelegate:delegate]; //This works better
}
[delegatesToRemove release];
}
The error suggests that, while some code somewhere is in the middle of going through your list, you are modifying the list (which explains the crash after addDelegate is called). If the code doing the enumerating is the one modifying the list, then you just have to put off the modifications until the enumeration is done (say, by collecting them up in a different list). Without knowing anything about the code doing the enumerating, can't say much more than that.
A simple solution, don't use a mutable set. They are dangerous for a variety of reasons, including this one.
You can use -copy and -mutableCopy to convert between mutable and non-mutable versions of NSSet (and many other classes). Beware all copy methods return a new object with a retain count of 1 (just like alloc), so you need to release them.
Aside from having less potential for bugs, non-mutable objects are faster to work with and use less memory.
[...]
static NSSet *_delegates = nil;
#implementation MyAppAPI
+ (void)initialize
{
if (self == [MyAppAPI class]) {
_delegates = [[NSSet alloc] init];
}
}
+ (void)addDelegate:(id)delegate
{
NSMutableSet *delegatesMutable = [_delegates mutableCopy];
[delegatesMutable addObject:delegate];
[_delegates autorelease];
_delegates = [delegatesMutable copy];
[delegatesMutable release];
}
+ (void)removeDelegate:(id)delegate
{
NSMutableSet *delegatesMutable = [_delegates mutableCopy];
[delegatesMutable removeObject:delegate];
[_delegates autorelease];
_delegates = [delegatesMutable copy];
[delegatesMutable release];
}
[...]
#end
Scott Hunter is right - it's a problem with modifying the NSSet while you're enumerating over the set's items. You should have a stack trace from where the application crashes. It probably has a line where you're adding to/remove from the _delegates set. This is where you need to make the modification. It's easy to do. Instead of adding to/deleting from the set, do the following:
NSMutableSet *tempSet = [_delegates copy];
for (id delegate in _delegates)
{
//add or remove from tempSet instead
}
[_delegates release], _delegates = tempSet;
Additionally, NSMutableSet is not thread safe, so you should call your methods always from the main thread. If you haven't explicitly added any extra threads, you have nothing to worry about.
A thing to always remember about the Objective-C "fast enumeration".
There is 2 big difference between "fast enumeration" and a for loop.
"fast enumeration" is quicker than a for loop.
BUT
You can't modify the collection your enumerating over.
You can ask your NSSet for - (NSArray *)allObjects and enumerate over that array while modifying your NSSet.
You get this error when a thread tries to modify (add,delete) the array while other thread is iterating over it.
One way to solve this using NSLock or synchronizing the methods. That ways add, remove and iterate methods cannot be called in parallel.
But this will have effect on performance and/or responsiveness because any add/delete will have to wait for the thread that was iterating over the array.
A better solution inspired from Java's CopyOnWriteArrayList would be to create a copy of the array and iterate over the copy. So the only change in your code will be:-
//better solution
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
NSArray *copyOfDelegates = [_delegates copy]
for (id delegate in copyOfDelegates)
{
if ([delegate respondsToSelector:#selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
Solution using locks with performance impact
//not a good solution
+ (void)addDelegate:(id)delegate
{
#synchronized(self){
[_delegates addObject:delegate];
}
}
+ (void)removeDelegate:(id)delegate
{
#synchronized(self){
[_delegates removeObject:delegate];
}
}
+ (void)didRecieveFeaturedItems:(NSArray*)items
{
#synchronized(self){
for (id delegate in _delegates)
{
if ([delegate respondsToSelector:#selector(didRecieveFeaturedItems:)])
[delegate didRecieveFeaturedItems:items];
}
}
}

How to release an object in a forin loop?

I'm new to cocoa / objective-c and i'm struggeling with the releases of my objects. I have the following code:
gastroCategoryList = [[NSMutableArray alloc] init];
for (NSDictionary *gastrocategory in gastrocategories) {
NSString *oid = [gastrocategory objectForKey:#"id"];
GastroCategory *gc = [[GastroCategory alloc] initWithId:[oid intValue] name:[gastrocategory objectForKey:#"name"]];
[gastroCategoryList addObject:gc];
}
The analyzer shows me that the "gastrocategory" defined in the for is a potential memory leak. But i'm not sure if i can release this at the end of the for loop?
Also at the following code:
- (NSArray *)eventsForStage:(int)stageId {
NSMutableArray *result = [[NSMutableArray alloc] init];
for (Event *e in eventList) {
if ([e stageId] == stageId) {
[result addObject:e];
}
}
return result;
}
The Analyzer tells me that my "result" is a potential leak. But where should I release this?
Is there also a simple rule to memorize when i should use assign, copy, retain etc. at the #property ?
Another problem:
- (IBAction)showHungryView:(id)sender {
GastroCategoriesView *gastroCategoriesView = [[GastroCategoriesView alloc] initWithNibName:#"GastroCategoriesView" bundle:nil];
[gastroCategoriesView setDataManager:dataManager];
UIView *currentView = [self view];
UIView *window = [currentView superview];
UIView *gastroView = [gastroCategoriesView view];
[window addSubview:gastroView];
CGRect pageFrame = currentView.frame;
CGFloat pageWidth = pageFrame.size.width;
gastroView.frame = CGRectOffset(pageFrame,pageWidth,0);
[UIView beginAnimations:nil context:NULL];
currentView.frame = CGRectOffset(pageFrame,-pageWidth,0);
gastroView.frame = pageFrame;
[UIView commitAnimations];
//[gastroCategoriesView release];
}
I don't get it, the "gastroCategoriesView" is a potential leak. I tried to release it at the end or with autorelease but neither works fine. Everytime I call the method my app is terminating. Thank you very much again!
In your loop, release each gc after adding it to the list since you won't need it in your loop scope anymore:
gastroCategoryList = [[NSMutableArray alloc] init];
for (NSDictionary *gastrocategory in gastrocategories) {
NSString *oid = [gastrocategory objectForKey:#"id"];
GastroCategory *gc = [[GastroCategory alloc] initWithId:[oid intValue] name:[gastrocategory objectForKey:#"name"]];
[gastroCategoryList addObject:gc];
[gc release];
}
In your method, declare result to be autoreleased to absolve ownership of it from your method:
NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
// An alternative to the above, produces an empty autoreleased array
NSMutableArray *result = [NSMutableArray array];
EDIT: in your third issue, you can't release your view controller because its view is being used by the window. Setting it to autorelease also causes the same fate, only delayed.
You'll have to retain your GastroCategoriesView controller somewhere, e.g. in an instance variable of your app delegate.
BoltClock's answer is spot-on as to the first part of your question. I'll try to tackle the rest.
Assign is for simple, non-object types such as int, double, or struct. It generates a setter that does a plain old assignment, as in "foo = newFoo". Copy & retain will, as their names imply, either make a copy of the new value ("foo = [newFoo copy]") or retain it ("foo = [newFoo retain]"). In both cases, the setter will release the old value as appropriate.
So the question is, when to copy and when to retain. The answer is... it depends. How does your class use the new value? Will your class break if some other code modifies the incoming object? Say, for example, you have an NSString* property imaginatively named "theString." Other code can assign an NSMutableString instance to theString - that's legal, because it's an NSString subclass. But that other code might also keep its own reference to the mutable string object, and change its value - is your code prepared to deal with that possibility? If not, it should make its own copy, which the other code can't change.
On the other hand, if your own code makes no assumptions about whether theString might have been changed, and works just as well whether or not it was, then you'd save memory by retaining the incoming object instead of unnecessarily making a copy of it.
Basically, the rule, which is unfortunately not so simple sometimes, is to think carefully about whether your own code needs its own private copy, or can correctly deal with a shared object whose value might be changed by other code.
The reason you can release gc after it is added to the gastroCategoryList is that when an object is added to an array, the array retains that object. So, even though you release your gc, it will still be around; retained by the gastroCategoryList.
When you are returning a newly created object from a method, you need to call autorelease. This will cause the object to be released only after the runtime leaves the scope of the calling method, thereby giving the calling method a chance to do something with the returned value.
Note that if your method starts with the word copy or new, then you should not autorelease your object; you should leave it for the calling method to release.
As for copy vs retain vs assign... as a general rule, copy objects that have a mutable version, such as NSArray, NSSet, NSDictionary, and NSString. This will ensure that the object you have a pointer to is not mutable when you don't want it to be.
Otherwise, use retain whenever you want your class to be ensured that an object is still in memory. This will apply to almost every object except for objects that are considered parents of your object, in which case you would use assign. (See the section on retain cycles here).
Also note that you have to use assign for non-object types such as int.
Read through the Memory Management Programming Guide a bit; it's quite helpful.

EXC_BAD_ACCESS issue

I am showing employee records as rows in a screen
I want the option to remove these and show a new set of records when I click a button
The structure / organization of my code is as follows :
In My controller .h
NSMutableArray *employeeScrollViewArray;
IBOutlet UIScrollView *employeeScrollViewItems;
Here I have a UIScrollView - employeeScrollViewItems - which is populated with rows shown on the UI
I also store these rows in an array - employeeScrollViewArray
Initialization and release of objects in MyController.m file is as shown:
In the method
- (void)viewDidLoad {
employeeScrollViewArray=[[NSMutableArray alloc]init];
I am releasing this mutable array in the method :
- (void)dealloc {
[employeeScrollViewArray release];
when the view loads I populate this array with objects from a user Defined class in a method :
EmployeeDetail.m file
EmployeeDetail *empDetail=[[EmployeeDetail alloc]init];
empDetail.delegate=self;
[[NSBundle mainBundle] loadNibNamed:#"EmployeeDetail" owner:empDetail options:nil];
[self.employeeScrollViewItems addSubview:empDetail.view];
[employeeScrollViewArray addObject:empDetail];
The details of the "h" file foe EmployeeDetail.h is :
#interface EmployeeDetail : UIViewController
UIView *view;
This is the "view" that is populated with data and shown on the screen
I now want to cleanup /remove these objects
So I tried things like this : ( which is then causing the exception )
int yyy = [employeeScrollViewArray count];
for(int xxx=yyy-1;xxx>=0;xxx--){
if ([[[employeeScrollViewItems subviews]objectAtIndex:xxx ]isKindOfClass:[UIView class]]) {
[[[employeeScrollViewItems subviews ]objectAtIndex:xxx] removeFromSuperview];
}
[employeeScrollViewArray removeObjectAtIndex:xxx];
}//for
This is the only way that I am able to remove these objects
If I try to remove them separately I get this error
However even after I remove the items and code passes - it still throws this error
If I remove this code then things work - but then I cannot remove these objects
Please help !
I don't know where the error is coming from, but I have some general advice:
You should release the object after adding it to the array.
[employeeScrollViewArray addObject:empDetail];
[empDetail release];
The array's entries will be retained by the array itself. In the cleanup method (dealloc) now just release the array: [employeeScrollViewArray release]. That will also release all contained objects.
Mostly it is not necessary to cleanup the view hierachy. However, if you want to remove all subviews of employeeScrollViewItems, I'd do this:
for (UIView* subview in [employeeScrollViewItems subviews]) {
[subview removeFromSuperview];
}

Custom UIButton Memory Management in dealloc

I am hoping to clarify the processes going on here.
I have created a subclass of UIButton whose init method looks like this:
- (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame {
self = [UIButton buttonWithType:UIButtonTypeCustom];
[self setTitle:title forState:UIControlStateNormal];
self.frame = btnFrame;
return self;
}
In my view controller I am creating one of these buttons and adding it as a subview:
myButton = [[CustomButton alloc] initWithTitle:#"Title" frame:someFrame];
[self.view addSubview:myButton];
In the view controller's dealloc method I log the retain count of my button:
- (void)dealloc {
NSLog(#"RC: %d", [myButton retainCount]); //RC = 2
[super dealloc];
NSLog(#"RC: %d", [myButton retainCount]); //RC = 1
}
The way I understand it, myButton is not actually retained, even though I invoked it using alloc, because in my subclass I created an autorelease button (using buttonWithType:).
In dealloc, does this mean that, when dealloc is called the superview releases the button and its retain count goes down to 1? The button has not yet been autoreleased?
Or do I need to get that retain count down to zero after calling [super dealloc]?
Cheers.
This deserves two answers.... one for the specific question and one for how memory is managed when the instance is replaced in -init (this one).
Initializers are an odd bird in the Objective-C memory management world. In effect, you are managing self. On entry, self is retained. On exit, you are expected to return either a retained object -- doesn't have to be the same object as self -- or nil.
So, breaking the standard idiom of [[[Foo alloc] init] autorelease] down:
id x = [Foo alloc]; // allocates instance with RC +1
x = [x init]; // RC is preserved, but x may differ
[x autorelease]; // RC -1 (sometime in future)
Note that all retain counts [RC] are expressed as deltas.
Thus, in the init method, you typically don't change the retain count of self at all!
However, if you want to return some other object, you need to release self and retain whatever you are going to return (whether allocated then or previously allocated somewhere else, say when an object is retrieved from a cache).
Specifically, with everything blown out into individual expressions because this answer is being overly pedantic:
- init {
[self release];
self = nil;
id newObject = [SomeClass alloc];
newObject = [newObject init];
if (newObject) {
self = newObject;
... initialize self here, if that is your fancy ...
}
return self;
}
This is more than a little bit tricky. I have summarized my answer in 5 parts:
Creating a custom init method that returns a different object
WARNING: beware of illegal memory access!
How to properly transfer ownership of the button to its parent view
Specific answers to specific questions
A suggestion for improvement
Part 1 : Creating a custom init method that returns a different object:
This is an example of a very special case, namely that the object returned from -initWithTitle:frame: is not the same "object" that was sent the message in the first place.
Normally speaking, the process goes like this:
instance = [Class alloc];
[instance init];
...
[instance release];
Of course, the alloc and init calls are usually grouped together into one line of code. The key thing to notice here is that the "object" (nothing more than an allocated block of memory at this point) which receives the call to init has already been allocated. If you return a different object (as in your example), you are responsible for releasing that original block of memory.
The next step would be to return a new object that has the proper retain count. Since you are using a factory method (+buttonWithType:), the resulting object has been autoreleased, and you must retain it to set the proper retain count.
Edit: A proper -init method should explicitly test to make sure that it is working with a properly initialized object before it does anything else with that object. This test was missing from my answer, but present in bbum's answer.
Here is how your init method should look:
- (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame {
[self release]; // discard the original "self"
self = [UIButton buttonWithType:UIButtonTypeCustom];
if (self == nil) { return nil; }
[self retain]; // set the proper retain count
[self setTitle:title forState:UIControlStateNormal];
self.frame = btnFrame;
return self;
}
Part 2: WARNING: beware of illegal memory access!
If you are allocating an instance of CustomButton, and then replacing it with an instance of UIButton, you could easily cause some very subtle memory errors. Let's say CustomButton has some ivars:
#class CustomButton : UIButton
{
int someVar;
int someOtherVar;
}
...
#end;
Now, when you replace the allocated CustomButton with an instance of UIButton in your custom init... method, you are returning a block of memory that is too small to hold a CustomButton, but your code will continue to treat this block of code as if it is a full-sized CustomButton. Uh oh.
For example, the following code is now very, very bad:
- (id)initWithTitle:(NSString *)title frame:(CGRect)btnFrame {
[self release]; // discard the original "self"
self = [UIButton buttonWithType:UIButtonTypeCustom];
[self retain]; // set the proper retain count
someOtherVar = 10; // danger, Will Robinson!
return self;
}
Part 3: How to properly transfer ownership of the button to its parent view:
As for your view controller's dealloc method, you will have to call [myButton release] if you have initialized the button as shown. This is to follow the rule that you must release anything that you alloc, retain or copy. A better way to deal with this issue is to let the controller's view take ownership of that button (which it does automatically when you add the button as a subview):
myButton = [[CustomButton alloc] initWithTitle:#"Title"
frame:someFrame]; // RC = 1
[self.view addSubview:myButton]; // RC = 2
[myButton release]; // RC = 1
Now, you never have to worry about releasing that button again. The view owns it, and will release it when the view itself is deallocated.
Part 4: Specific answers to specific questions:
Q: The way I understand it, myButton is not actually retained, even though I invoked it using alloc, because in my subclass I created an autorelease button (using buttonWithType:).
Correct.
Q: In dealloc, does this mean that, when dealloc is called the superview releases the button and its retain count goes down to 1? The button has not yet been autoreleased?
Also correct.
Q: Or do I need to get that retain count down to zero after calling [super dealloc]?
Sort of :) The retain count may or may not drop down to zero at the point when you log it. Autoreleased objects may still have a retain count of one, since they effectively belong to the autorelease pool for the current run loop. For that matter, the view itself may still belong to a window which has not yet been released. The only thing you really need to worry about is balancing out your own memory management. See Apple's memory management guide for details. From the point of view of your viewController, you have allocated the button once, so you must release it exactly once. When it comes to your custom init... method, things get a little bit trickier, but the principle is the same. A block of memory has been allocated, so it must be released (part 1), and, (part 2) init should return an object with a retain count of one (to be properly released later on).
Part 5: A suggestion for improvement:
You could avoid most of the custom initializer mess by simply creating your own factory method in the same spirit as the one provided by UIButton:
+ (id)buttonWithTitle:(NSString *)title frame:(CGRect)btnFrame {
UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom];
[button setTitle:title forState:UIControlStateNormal];
button.frame = btnFrame;
return button;
}
Note that this approach can still result in memory access errors as identified in part 2
First:
Do not call retainCount
The absolute retain count of an object is next to useless. There are always better ways to reason about memory management in your application.
Next:
Your initWithTitle:frame: method is allocating and returning an instance of UIButton, not an instance of the subclass. If that is what you want, there is no need for a subclass at all.
If you really want an instance of a subclass of UIButton, that is going to be more difficult. A quick google search and a read of the documentation indicates that UIButton really isn't intended to be subclassed.
I just tried:
#interface FooButton:UIButton
#end
#implementation FooButton
#end
FooButton *f = [FooButton buttonWithType: UIButtonTypeDetailDisclosure];
NSLog(#"%#", f);
And it printed:
<UIButton: 0x9d03fa0; frame = (0 0; 29 31); opaque = NO; layer = <CALayer: 0x9d040a0>>
I.e. the sole method to be used to create UIButton instances quite explicitly does not allocate via [self class]. You could go down the path of trying to initialize the instance by hand ala UIView or UIControl, but that is likely a lot of trouble.
What are you trying to do?

Instruments Living Count and/or Retain Count Issue

I'm running into a retain count issue that I do not understand. (I've added what I believe to be the retain count of vertex in [] at the end of each line of code).
CBVertex *vertex = nil;
for(int i=0; i<10; i++) {
vertex = [[CBVertex alloc] initWithFrame:CGRectMake(minX, y, 10.0, 10.0)]; // retain count [1]
[vertex setTag:i];
[vertex setAnimationDelegate:self];
[gameboard addSubview:vertex]; // retain count [2]
[tripGraph addVertex:vertex]; // retain count [3]
[vertex release]; vertex=nil; // retain count [2]
}
CBVertex is a subclass of UIView, gameboard is a UIView and tripGraph is a class that, among other things, has an NSMutableArray (privateVerticies) to which vertex is added to in its addVertex method.
After the above is executed, Instruments shows that there are 10 instances of CBVertex living.
Later in the code execution (I've confirmed that this code executes):
[[tripGraph verticies] makeObjectsPerformSelector:#selector(removeFromSuperview)];
// gameboard should have no references to any of the CBVertex's (correct??)
[tripGraph removeAllVerticies];
// tripGraph privateVerticies is empty and no references to any of
// the CBVertex's (correct?)
Relevant tripGraph methods:
-(NSArray *) verticies {
return [NSArray arrayWithArray:privateVerticies];
}
-(void) tripGraph removeAllVerticies {
[privateVerticies removeAllObjects];
}
- (void) addVertex:(CBVertex *)vertex {
[privateVerticies addObject:vertex];
}
The issue arises when the second set of CBVertex's are created. Instruments shows that the first set of CBVertex's is still live (i.e. the number of instances of CBVertexs is now 20).
I'm (obviously?) missing a release somewhere, but don't understand where . . .
Help/pointers are appreciated!!
thanks
tom
If gameboard and tripGraph are still retaining that CBVertex object, then even if you release it in your loop, it will continue to exist until you remove the CBVertex object from gameboard and tripGraph as well.
OK, I'm closing this out as the code above is correct. The problem lies in my overriden removeFromSuperview method in which there is an animation going on. I'm going to investigate further and if I don't have it figured out, I'll repost a new question. (and link to it from here).
Thanks for the comments, answer and several views.
For those interested, here's what was going on and how it was resolved.
In CBVertex, I've overridden removeFromSuperview. In that overridden method, I am animating the view's layer and setting the view as the CAAnimations delegate (which is retained by the CAAnimation) and calling super removeFromSuperview. The animation is not removed on completion.
As the animation retains the delegate and the animation is not removed, the view's retain count remains at +1.
My resolution was to create an intermediate method to perform the animation. When the animation is complete it calls the overriden removeFromSuperview, which now only removes all animations and calls super. Removing the animation releases it, which in turn releases it's reference to it's delegate (the CBVertex) and the CBVertex's retain count goes to +0.
One final thought for anyone chasing retain counts: don't think of them as absolute values. The inner workings of the objects you may be using might be retaining your instances more than you'd expect -- think of retain counts as a delta.
SomeClass *myObject = [[SomeClass alloc] init]; // retain count +1
[someMutableSet addObject:myObject]; // retain count +2