Revealed on: September 18, 2025
As a developer who makes use of Swift frequently, [weak self]
needs to be one thing that is nearly muscle reminiscence to you. I’ve written about utilizing [weak self]
earlier than within the context of when it is best to usually seize self
weakly in your closures to keep away from retain cycles. The underside line of that put up is that closures that are not @escaping
will often not want a [weak self]
as a result of the closures aren’t retained past the scope of the perform you are passing them to. In different phrases, closures that are not @escaping
do not often trigger reminiscence leaks. I am positive there are exceptions however usually talking I’ve discovered this rule of thumb to carry up.
This concept of not needing [weak self]
for all closures is strengthened by the introduction of SE-0269 which permits us to leverage implicit self
captures in conditions the place closures aren’t retained, making reminiscence leaks unlikely.
Later, I additionally wrote about how Process
situations that iterate async sequences are pretty prone to have reminiscence leaks as a consequence of this implicit utilization of self.
So how can we use [weak self]
on Process
? And if we should not, how can we keep away from reminiscence leaks?
On this put up, I intention to reply these questions.
The fundamentals of utilizing [weak self]
in completion handlers
As Swift builders, our first intuition is to do a weak -> robust dance in just about each closure. For instance:
loadData { [weak self] knowledge in
guard let self else { return }
// use knowledge
}
This method makes a whole lot of sense. We begin the decision to loadData
, and as soon as the info is loaded our closure is named. As a result of we need not run the closure if self
has been deallocated throughout our loadData
name, we use guard let self
to ensure self
continues to be there earlier than we proceed.
This turns into more and more vital once we stack work:
loadData { [weak self] knowledge in
guard let self else { return }
processData(knowledge) { [weak self] fashions in
// use fashions
}
}
Discover that we use [weak self]
in each closures. As soon as we seize self
with guard let self
our reference is robust once more. Which means for the remainder of our closure, self
is held on to as a powerful reference. Resulting from SE-0269
we are able to name processData
with out writing self.processData
if we’ve got a powerful reference to self.
The closure we go to processData
additionally captures self
weakly. That is as a result of we do not need that closure to seize our robust reference. We’d like a brand new [weak self]
to forestall the closure that we handed to processData
from making a (shortly lived) reminiscence leak.
After we take all this data and we switch it to Process
, issues get attention-grabbing…
Utilizing [weak self]
and unwrapping it instantly in a Process
To illustrate that we need to write an equal of our loadData
and processData
chain, however they’re now async
features that do not take a completion handler.
A typical first method could be to do the next:
Process { [weak self] in
guard let self else { return }
let knowledge = await loadData()
let fashions = await processData(knowledge)
}
Sadly, this code doesn’t clear up the reminiscence leak that we solved in our authentic instance.
An unstructured Process
you create will begin operating as quickly as potential. Which means if we’ve got a perform like beneath, the duty will run as quickly because the perform reaches the top of its physique:
func loadModels() {
// 1
Process { [weak self] in
// 3: _immediately_ after the perform ends
guard let self else { return }
let knowledge = await loadData()
let fashions = await processData(knowledge)
}
// 2
}
Extra complicated name stacks would possibly push the beginning of our process again by a bit, however usually talking, the duty will run just about instantly.
The issue with guard let self
in the beginning of your Process
As a result of Process
in Swift begins operating as quickly as potential, the prospect of self
getting deallocated within the time between creating and beginning the duty is very small. It isn’t inconceivable, however by the point your Process
begins, it is seemingly self
continues to be round it doesn’t matter what.
After we make our reference to self
robust, the Process
holds on to self
till the Process
completes. In our name that signifies that we retain self
till our name to processData
completes. If we translate this again to our outdated code, here is what the equal would seem like in callback based mostly code:
loadData { knowledge in
self.processData(knowledge) { fashions in
// for instance, self.useModels
}
}
We do not have [weak self]
wherever. Which means self
is retained till the closure we go to processData
has run.
The very same factor is going on in our Process
above.
Typically talking, this is not an issue. Your work will end and self
is launched. Possibly it sticks round a bit longer than you want but it surely’s not a giant deal within the grand scheme of issues.
However how would we stop kicking off processData
if self
has been deallocated on this case?
Stopping a powerful self within your Process
We may ensure that we by no means make our reference to self
into a powerful one. For instance, by checking if self
continues to be round by means of a nil
verify or by guarding the results of processData
. I am utilizing each strategies within the snippet above however the guard self != nil
might be omitted on this case:
Process { [weak self] in
let knowledge = await loadData()
guard self != nil else { return }
guard let fashions = await self?.processData(knowledge) else {
return
}
// use fashions
}
The code is not fairly, however it will obtain our objective.
Let’s check out a barely extra complicated situation that entails repeatedly fetching knowledge in an unstructured Process
.
Utilizing [weak self]
in an extended operating Process
Our authentic instance featured two async calls that, based mostly on their names, most likely would not take all that lengthy to finish. In different phrases, we have been fixing a reminiscence leak that may usually clear up itself inside a matter of seconds and you might argue that is not truly a reminiscence leak value fixing.
A extra complicated and attention-grabbing instance may look as follows:
func loadAllPages() {
// solely fetch pages as soon as
guard fetchPagesTask == nil else { return }
fetchPagesTask = Process { [weak self] in
guard let self else { return }
var hasMorePages = true
whereas hasMorePages && !Process.isCancelled {
let web page = await fetchNextPage()
hasMorePages = !web page.isLastPage
}
// we're achieved, we may name loadAllPages once more to restart the loading course of
fetchPagesTask = nil
}
}
Let’s take away some noise from this perform so we are able to see the bits which might be truly related as to whether or not we’ve got a reminiscence leak. I wished to point out you the total instance that will help you perceive the larger image of this code pattern…
Process { [weak self] in
guard let self else { return }
var hasMorePages = true
whereas hasMorePages {
let web page = await fetchNextPage()
hasMorePages = !web page.isLastPage
}
}
There. That is a lot simpler to have a look at, is not it?
So in our Process
we’ve got a [weak self]
seize and instantly we unwrap with a guard self
. You already know this would possibly not do what we would like it to. The Process
will begin operating instantly, and self
will probably be held on to strongly till our process ends. That mentioned, we do need our Process
to finish if self
is deallocated.
To realize this, we are able to truly transfer our guard let self
into the whereas
loop:
Process { [weak self] in
var hasMorePages = true
whereas hasMorePages {
guard let self else { break }
let web page = await fetchNextPage()
hasMorePages = !web page.isLastPage
}
}
Now, each iteration of the whereas loop will get its personal robust self
that is launched on the finish of the iteration. The following one makes an attempt to seize its personal robust copy. If that fails as a result of self
is now gone, we get away of the loop.
We fastened our drawback by capturing a powerful reference to self
solely once we want it, and by making it as short-lived as potential.
In Abstract
Most Process
closures in Swift do not strictly want [weak self]
as a result of the Process
usually solely exists for a comparatively quick period of time. In the event you discover that you just do need to ensure that the Process
would not trigger reminiscence leaks, it is best to ensure that the primary line in your Process
is not guard let self else { return }
. If that is the primary line in your Process
, you are capturing a powerful reference to self
as quickly because the Process
begins operating which often is nearly instantly.
As an alternative, unwrap self
solely whenever you want it and be sure to solely hold the unwrapped self
round as quick as potential (for instance in a loop’s physique). You can additionally use self?
to keep away from unwrapping altogether, that approach you by no means seize a powerful reference to self
. Lastly, you might contemplate not capturing self
in any respect. In the event you can, seize solely the properties you want in order that you do not depend on all of self
to stay round whenever you solely want elements of self
.