removeAllObjects able to remove added retain cycled objects


#1

Following is a small snippet out of my TouchTracker main code where i’m having an issue related to retain cycle. [rootObj.completeLines removeAllObjects] has been taken from clearAllLines method. The snippet below is based on the control flow that the running program follows and thus is not a chunk out of the TouchDrawView.m line by line.

[rootObj.completeLines addObject:line]; [line setContainingArray:rootObj.completeLines]; [rootObj.completeLines removeAllObjects];// the issue..
In the above snippet rootObj.completeLines is an NSMutableArray to which a ‘Line’ object has been added. containingArray is a strong type pointer/property. Clearly this forms a retain cycle between the NSMutableArray object and the ‘Line’ object… In the next line i pass the removeAllObjects message to the array… My question is, how come this message be able to purge an object (Line in this case) which is engaged in a retain cycle with another object (the Array). Clearly i must be missing something crucial here. Pls assist…


#2

Good question. Let me see if I can help.

Like other NS collections, the NSMutableArray holds a strong reference to the objects it stores. This means that as long as the array is in existence, it will retain its items. Next, you added a line object to the array, and set that line’s property to point (strongly) back to the array. This is a retain cycle (strong reference cycle, whichever you prefer). But in your next line, you “removeAllObjects.” This, according to the documentation, “empties the array of all its objects.” Thus, the array no longer retains its items. It cuts all ties with those objects.

So what happens? Your line object dies, even though it’s pointing back strongly to the array. Why?

In ARC, an object has a retain count. The retain count is the sum of how many objects are strongly pointing to it, or in other words, “owning” the object. Any object with a retain count of 1 or more lives to see another nanosecond (it sticks around). But when an object’s retain count hits 0, it’s game over, and the object is destroyed or 'dealloc’ed. When you added the line to the array, the array kept a pointer pointing to the line object, so the line’s retain count was set to 1. But when the array removes all objects, it no longer stores a pointer to that line, and so the line’s retain count is decremented. If the array is the sole object owning or pointing to your line, then the line’s retain count goes to 0, and it’s destroyed.

There is no retain cycle in this case. That’s why the book told us to set the array to a newly allocated array instead of emptying it–this instead would cause the array pointer to point to a different array altogether, and the original array and the line object would be marooned, locked forever in a retain cycle. But since you removed the objects, you never actually had a memory-leaking retain cycle.

Does that help clear things up?


#3

Great explanation. Well done!


#4

Apologies for such a late response. I could only comment when i read your answer. Thanks a ton for answering my question. I hadn’t followed this thread in yrs.
I think I understand what you mean here. So correct me if I’m wrong here in my understanding of this- The individual ‘id’ pointers maintained by the NSMutableArray are actually the ones directly pointing to the lines objects and not the array object itself. So when “removeAllObjects” message is received by the same array, what happens is that the ‘id’ pointers point to NULL and so the lines objects now become unowned. The only connection remaining from this point forward, between the individual lines and the array, is the “containingArray” back-pointer pointed at the latter by the former. But since the lines are now ownerless, they get dealloc’d and the ivar back-pointers- “containingArray” also follow suit.
Were we to not invoke the “removeAllObjects” message, the network of connections between the array and the lines would form a complex cycle, rather than a simple direct one that can be usually found between 2 individual objects.