Knowing Where Retain Cycles Are and Removing Them

Knowing where retain cycles are and removing them

If you are using Xcode 8 or above, you can use the memory graph thingy to see what object holds a reference to what object.

To see the memory graph, first build and run your app with Xcode. When you want to check whether all the instances you created are discarded properly, go to this tab on the left pane:

Sample Image

Then press the button on the right there:

Sample Image

After that, select the bottom-most option - View Memory Graph Hierarchy:

Sample Image

Now it will show you all the objects that are in memory:

Sample Image

In my case, I have a GameSystem object, 6 ButtonNode objects and a few others. You'll notice that there is a little ! beside the GameSystem object. That means this object is leaked. Also, GameScene should not be in memory anymore because the current scene is TitleScene. Let's see what is retaining it by clicking on the instance:

Sample Image

Now you can clearly see that it is retained by a closure!

That is how you use the memory graph to see where you should put weak-references and avoid retain cycles.

Understanding retain cycles in RxSwift

Yes there is a retain cycle because setEmptyViewShown(_:) is a method which takes self as an implicit first argument.

Better would be something like:

disposeBag.insert(
showEmptyView.bind(to: emptyView.rx.isHidden),
showEmptyView.map { !$0 }.bind(to: tableView.rx.isHidden)
)

Swift Retain Cycles and Closures

Neither of these will create retain cycles since the block is not attached to self. Additionally, retain cycles with guaranteed life span aren't the worst thing in the world.

Let's consider some examples. (sorry, my swift isn't particularly strong, so I'm going to revert to obj-c.)


- (void)doSomething:(void(^)())block {
self.block = block;
}

// elsewhere
[self doSomething:^{
self.someProperty = 5;
}];

This creates a retain cycle because self is referenced (not weakly) within a block to which self holds a reference.


[UIView transitionWithView:self duration:5, options:UIViewAnimationOptionTransitionCrossDissolve animations:^{
[self setNeedsDisplay];
} completion:nil];

This does not create a retain cycle because the animations block is sent off to the system -- your UIView subclass doesn't hold a reference to the block.


Side note: You shouldn't need to have a return at the end of your closure, but I guess swift is ridiculous and tries to be "helpful". Also, I'm not sure you need to call self.setNeedsDisplay() during the animation since it should be doing that itself...

Why doesn't Swift ARC break property references first to prevent retain cycles?

You ask:

I'm just curious about the detail of what happens with ARC during an automated destruction phase e.g. finishing the execution of a function.

When you have a local variable that is referencing an instance of an object, it establishes a strong reference to that object. When the local variable falls out of scope, it releases its strong reference. And if that was the last strong reference to the object, that object will be deallocated.

func runTasks() {
var person = Person(name: "Reuben")

// At this point, the `Person` object has one strong reference, as does the `MacBook`

doSomething(with: person)

// The `person` variable falls out of scope, the sole strong reference to the `Person`
// instance will be relieved, and the `Person` object will therefore be deallocated.
}

Perhaps properties references aren't accessed/destroyed because they are part of the object?

When an object is deallocated, any of its properties that had their own strong references to something else are automatically released, too.

func runTasks() {
var person = Person(name: "Reuben")
var computer = Macbook(model: "Pro 2020")

// At this point, the `Person` object has one strong reference, as does the `MacBook`

person.macbook = computer

// Now the `Macbook` instance has two strong references, the local variable and the `Person`

doSomething(with: person)

// When `person` and `computer` variables the local variables fall out of scope
// at the end of this function, their respective strong references to the `Person`
// and the `Macbook` objects will be released. But since there are no more strong
// references to `Person`, it will be deallocated. But when it is deallocated,
// its strong reference to `Macbook` will be released automatically, too. So now,
// the two strong references to that `Macbook` object are now released, too (both
// the `computer` local variable and the `macbook` property of `Person`), so it will
// deallocated, too.
}

Now, as you have identified, a strong reference cycle (previously called a “retain cycle”) is when two or more objects are keeping strong references to each other (thus, unless you manually nil one or more of those references, they will end up with lingering strong references to each other and therefore neither will be deallocated until the cycle is broken).

It might seem appealing for the memory management system to identify and break these strong reference cycles for us at runtime, but that is computationally impractical. It would have to build a memory graph, identifying which objects are referencing each other, and somehow figure out which reference to break. There are even scenarios where we might have some short-lived cycle that we absolutely would not want the OS to resolve for us (e.g. you create a dispatch queue, dispatch some blocks of work, and we rely on the queue to not be released until the dispatched blocks are finished; URLSession is another example).

Fortunately, while this sort of constant memory graph analysis is not feasible at runtime, Xcode does offer a debugging tool to help identify these scenarios, namely, the “debug memory graph” feature. For more information, see How to debug memory leaks when Leaks instrument does not show them? or How can identify strong reference cycles in Swift? or iOS app with ARC, find who is owner of an object.

Fortunately, while we have wonderful diagnostic tools to find these strong reference cycles, preventing them in the first place is very easy, by breaking the cycles with weak or unowned references. The result is a highly performant, very simple memory management infrastructure, where cycles are easily avoided with weak/unowned, but we have some excellent debugging tools for identifying issues, when needed.

Can any one explain Retain cycle with example code(Objective C) ? and How can we remove retain Cycle ?(with code)

Delegation is one example where you have to avoid a retain cycle by using the assign attribute on a delegate property. For example, you have a parent object which creates a child:

self.child = [[[Child alloc] init] autorelease];

So the parent has a retained reference to the child (because the property setter retains it).

Now the parent sets itself as a delegate on the child:

self.child.delegate = self;

Now, if the child retains its delegate property there is a retain cycle. Both contain references to the other and cannot be deallocated.

To avoid this the child declares the delegate property with the assign attribute:

@property (nonatomic, assign) id delegate;

This is safe because a delegate will almost always outlive the delegator. If not, the parent should set the child's delegate to nil before it goes away.

Core Data - break retain cycle of the parent context

I looked at your sample project. Kudos for posting.

First, the behavior you are seeing is not a bug... at least not in Core Data. As you know, relationships cause retain cycles, that must be broken manually (documented here: https://developer.apple.com/library/mac/#documentation/cocoa/Conceptual/CoreData/Articles/cdMemory.html).

Your code is doing this in didSave:. There may be better places to break the cycle, but that's a different matter.

Note that you can easily see what objects are registered in a MOC by looking at the registeredObjects property.

Your example, however, will never release the references in the root context, because processPendingEvents is never called on that MOC. Thus, the registered objects in the MOC will never be released.

Core Data has a concept called a "User Event." By default, a "User Event" is properly wrapped in the main run loop.

However, for MOCs not on the main thread, you are responsible for making sure user events are properly processed. See this documentation: http://developer.apple.com/library/ios/#documentation/cocoa/conceptual/CoreData/Articles/cdConcurrency.html, specifically the last paragraph of the section titled Track Changes in Other Threads Using Notifications.

When you call performBlock the block you give it is wrapped inside a complete user-event. However, this is not the case for performBlockAndWait. Thus, the private-context MOC will keep those objects in its registeredObjects collection until processPendingChanges is called.

In your example, you can see the objects released if you either call processPendingChanges inside the performBlockAndWait or change it to performBlock. Either of these will make sure that the MOC completes the current user-event and removes the objects from the registeredObjects collection.

Edit

In response to your edit... It is not that the first one does not dealloc the objects. It's that the MOC still has the objects registered as faults. That happened after the save, during the same event. If you simply issue a no-op block [context performBlock:^{}] you will see the objects removed from the MOC.

Thus, you don't need to worry about it because on the next operation for that MOC, the objects will be cleared. You should not have a long-running background MOC that is doing nothing anyway, so this really should not be a big deal to you.

In general, you do not want to just refresh all objects. However, if you do you want to remove all objects after being saved, then your original concept, of doing it in didSave: is reasonable, as that happens during the save process. However, that will fault objects in all contexts (which you probably don't want). You probably only want this draconian approach for the background MOC. You could check object.managedObjectContext in the didSave: but that's not a good idea. Better would be to install a handler for the DidSave notification...

id observer = [[NSNotificationCenter defaultCenter]
addObserverForName:NSManagedObjectContextDidSaveNotification
object:rootContext
queue:nil
usingBlock:^(NSNotification *note) {
for (NSManagedObject *mo in rootContext.registeredObjects) {
[rootContext refreshObject:mo mergeChanges:NO];
}
}];

You will see that this probably gives you what you want... though only you can determine what you are really trying to accomplish.

Retain cycle on `self` with blocks

Strictly speaking, the fact that it's a const copy has nothing to do with this problem. Blocks will retain any obj-c values that are captured when they are created. It just so happens that the workaround for the const-copy issue is identical to the workaround for the retain issue; namely, using the __block storage class for the variable.

In any case, to answer your question, there's no real alternative here. If you're designing your own block-based API, and it makes sense to do so, you could have the block get passed the value of self in as an argument. Unfortunately, this doesn't make sense for most APIs.

Please note that referencing an ivar has the exact same issue. If you need to reference an ivar in your block, either use a property instead or use bself->ivar.


Addendum: When compiling as ARC, __block no longer breaks retain cycles. If you're compiling for ARC, you need to use __weak or __unsafe_unretained instead.

Best way to find retain cycle?

You could check out if instruments detects the leak, like so:
Sample Image

From the looks of it, though, that timer near the end of the list doesn't get released.
Could you update your post with the code in setVideoState ?

Edit:

What I usually do is try and match up the retain/releases (instruments should actually do that for you, i think it was some filter in the menu to the left), while focusing on my code, because the frameworks usually have their things together. In your case, as you go down that list you see that the timer has +1 but never get released after that.



Related Topics



Leave a reply



Submit