Block Retain Cycles in Swift

Block retain cycles in Swift?

To prevent a block from holding a strong reference to an object, you must define a capture list for the block.

The closure expression syntax is defined as follows:

{ ( /*parameters*/ ) -> /*return type*/ in

// statements
}

But this is extended later in the documentation to include a capture list. This effectively equates to the expression syntax being defined as follows:

{ [ /*reference type*/ /*object*/, ... ] ( /*parameters*/ ) -> /*return type*/ in

// statements
}

...where /*reference type*/ can be either weak or unowned.

The capture list is the first thing to appear in the closure and it is optional. The syntax, as shown above is defined as one or more pairs of reference type followed by object; each pair is separated by a comma. For example:

[unowned self, weak otherObject]

Complete example:

var myClosure = {
[unowned self] in
print(self.description)
}

Note that an unowned reference is non-optional, so you don't need to unwrap it.

Hopefully that answers your question. You can read up more about ARC in Swift in the relevant section of the documentation.

You should pay particular attention to the difference between weak and unowned. It could be safer in your implementation to use weak, because using unowned assumes the object will never be nil. This may lead to your app crashing if the object has actually been deallocated before being used in your closure.

Using weak as the reference type, you should unwrap with ?, as follows:

var myClosure = {
[weak self] in
print(self?.description)
}

Swift closures causing strong retain cycle with self

Yes, that can still cause a retain cycle.

The simplest retain cycle is 2 objects that each have strong references to each other, but 3-way and larger retain cycles are also possible.

In your case, you have view controller who's view contains a button (a strong reference.) The button has a strong reference to a closure. The closure strongly references the view controller using self. So the view owns the button. The button owns the closure. The closure owns the view controller. If you dismiss the view controller (say it was a modal) then it SHOULD be deallocated. However, since you have this 3-way retain cycle, it won't be deallocated. You should add a deinit method to your view controller with a print statement and try it.

The solution is to add a capture list (The [weak self] bit) just like you did in your first example.

Note that a common pattern is to add a capture list, and then map the weak variable to a strong variable inside the closure:

let myClosure = { [weak self] in 
guard let strongSelf = self else { return }
//...
strongSelf.doSomething()
}

That way, if the closure is still active but the object that owns it was released, the guard statement at the beginning detects that self is nil and exits at the beginning of the closure. Otherwise you have to unwrap the optional every time you refer to it.

In certain cases it's also possible that the object in the capture list (self in these examples) could be deallocated in the middle of the closure being executed, which can cause unpredictable behavior. (Gory details: This is only when the closure is run on a different thread than the owner of the object in the capture list, but completion handlers are pretty commonly run on a background thread, so it does happen)

Imagine this:

let myClosure = { [weak self] in 

self?.step1() //1

//time-consuming code

self?.property = newValue //2

//more time-consuming code

self?.doSomething() //3

//even more time-consuming code

self?.doSomethingElse() //4
}

With the code above, if the closure is run on a background thread, its possible that self would still be valid at step 1, but by the time you execute step 2, self has been deallocated. The same goes for steps 3 and 4. By adding the guard strongSelf = self else { return } at the beginning of the closure you test at the entry of the closure to make sure self is still valid, and if it is, you make the closure create a strong reference that only lives as long as the closure takes to run, and that prevents self from being deallocated while the closure code is executing.)

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

Weird weak self and retain cycle behaviour

First of all, all printers are creating and retaining their own Delayer. The delayer takes a closure and, in turn, retains that closure.

Let's try to walk through them one by one.

Printer1

As you stated yourself, it's pretty clear why it's creating a retain cycle. You are passing the self.action instance method as the closure to the Delayer, and since all closures are reference types, passing self.action will retain its surrounding scope (which is Printer1).

Printer2

Again, pretty obvious here. You're explicitly capturing a weak reference to self inside the closure you're passing to Delayer, hence not creating a retain cycle.

Printer3

Here, a retain cycle is not created, because the self.weakAction property is called immediately, and its result (a closure which holds a weak reference to self) is passed on to Delayer. This, in effect, is the exact same thing as what's happening in Printer2.

Printer4

First, you're capturing a weak reference to self, and then fetching welf?.action and passing the result into Delayer. Again, welf?.action is called immediately, and the result (a pointer to an instance method) is passed on to Delayer. The weak reference to self is only kept for the duration of the surrounding scope (the lazy var creation scope), and passing the action instance method will retain self. This is identical to Printer1.

Printer5

Here, you're first creating a weak reference to self, and then you're capturing that weak reference inside a new closure that is passed to Delayer. Since self is never directly referenced in the passed closure, it will not capture self in that scope, only the welf weak reference. This is pretty much identical to Printer2, but with a slightly different syntax.

Personally, I would opt for the Printer2 way (creating a new closure, retaining a weak reference to self and using that to call self?.action). It makes for the easiest code to follow (as opposed to retaining a variable with a closure that weakly captures self). But, depending on what you're actual use case is, it might of course make sense.

iOS: Blocks, main queue, and retain cycles

The "cycle" part does seem to be overlooked when people worry about retain cycles. In your code, the block is managed by GCD, your objects don't own it, so there is no retain cycle.

we've been over using the weakself pattern

It does seem the case that a lot of people think "aargh! Blocks! Make a weakSelf"!

The retain cycles happen when an object owned by the block (so, a strong reference to the object or one of its ivars) itself owns the block (so the block is copied to a property or ivar).

The typical case is when you've made an object such as a custom alert view that has a completion handler block, and within the block you do something with the object itself (e.g. remove the view from its superview).

Completion block of an object - retain cycle?

You do have a retain cycle so please use the weakSelf in your completion block:

Why?

Blocks always retain every object that is passed along with them.
You are strong referencing the pageViewontroller that holds a block that is retaining your viewcontroller.

So, these objects are circular referencing eachother so one of them has to have a weak reference. Otherwise, your objects will not be deallocated.

Also you shouldn't worry about the weakSelf reference because:
Since you are retaining the pageController in your viewController, it will be 100% certain that when your completionblock gets executed, your "weakSelf" instance is still alive.

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.

Why Is There No Retain Cycle In ReceiveValue Block Combine Subscription

The closure, as you expected, does retain a strong reference to self. The closure itself is maintained by the Sink subscriber.

If nothing else happens, this is a memory leak because the subscriber is never cancelled, because AnyCancellable is never released, because self never de-inits, and self never de-inits because the subscriber is holding a reference it.

However, in your case, the publisher completes, and that's another way for the subscriber to release its closures. So, self is only released after the pipeline completes.

To illustrate, we can use a PassthroughSubject to explicitly send a completion:

class Foo {
var c: AnyCancellable? = nil

func fetch() {
let subject = PassthroughSubject<String, Never>()

c = subject.sink {
self.c // capture self
print($0)
}

subject.send("sync")

DispatchQueue.main.async { subject.send("async") }

DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
subject.send("async 2 sec")
subject.send(completion: .finished)
}
}

deinit { print("deinit") }
}


do {
Foo().fetch()
}

Because self is captured, it's not released until after a completion is sent 2 seconds later:

sync
async
async 2 sec
deinit

If you comment out the line subject.send(completion: .finished), there will not be a deinit:

sync
async
async 2 sec

If you use [weak self] in the closure, the pipeline would cancel:

sync
deinit


Related Topics



Leave a reply



Submit