21.7 C
Canberra
Tuesday, October 21, 2025

The right way to unwrap [weak self] in Swift Concurrency Duties? – Donny Wals


Revealed on: September 18, 2025

As a developer who makes use of Swift frequently, [weak self] must 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 typically seize self weakly in your closures to keep away from retain cycles. The underside line of that publish 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 operate you are passing them to. In different phrases, closures that are not @escaping do not often trigger reminiscence leaks. I am certain there are exceptions however typically 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 Job situations that iterate async sequences are pretty more likely to have reminiscence leaks as a result of this implicit utilization of self.

So how can we use [weak self] on Job? And if we should not, how can we keep away from reminiscence leaks?

On this publish, I goal 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] information in 
  guard let self else { return }

  // use information
}

This method makes a variety of sense. We begin the decision to loadData, and as soon as the information is loaded our closure known as. 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 verify self remains to be there earlier than we proceed.

This turns into more and more vital once we stack work:

loadData { [weak self] information in 
  guard let self else { return }

  processData(information) { [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 powerful once more. Which means for the remainder of our closure, self is held on to as a powerful reference. As a result of SE-0269 we are able to name processData with out writing self.processData if now we have 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 want 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 information and we switch it to Job, issues get attention-grabbing…

Utilizing [weak self] and unwrapping it instantly in a Job

For instance that we wish 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 can be to do the next:

Job { [weak self] in
  guard  let self else { return }

  let information = await loadData()
  let fashions = await processData(information)
}

Sadly, this code doesn’t remedy the reminiscence leak that we solved in our unique instance.

An unstructured Job you create will begin working as quickly as potential. Which means if now we have a operate like under, the duty will run as quickly because the operate reaches the top of its physique:

func loadModels() {
  // 1
  Job { [weak self] in
    // 3: _immediately_ after the operate ends
    guard  let self else { return }

    let information = await loadData()
    let fashions = await processData(information)
  }
  // 2
}

Extra advanced name stacks would possibly push the beginning of our activity again by a bit, however typically talking, the duty will run just about instantly.

The issue with guard let self in the beginning of your Job

As a result of Job in Swift begins working 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 not possible, however by the point your Job begins, it is doubtless self remains to be round it doesn’t matter what.

After we make our reference to self robust, the Job holds on to self till the Job 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 { information in 
  self.processData(information) { 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 Job above.

Usually talking, this is not an issue. Your work will end and self is launched. Perhaps it sticks round a bit longer than you would like however it’s not an enormous 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 inside your Job

We may make it possible for we by no means make our reference to self into a powerful one. For instance, by checking if self remains to be round by means of a nil test 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:

Job { [weak self] in
  let information = await loadData()
  guard self != nil else { return }

  guard let fashions = await self?.processData(information) else {
    return
  }

  // use fashions
}

The code is not fairly, however it might obtain our purpose.

Let’s check out a barely extra advanced challenge that entails repeatedly fetching information in an unstructured Job.

Utilizing [weak self] in an extended working Job

Our unique 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 might sometimes remedy itself inside a matter of seconds and you could possibly argue that is not really a reminiscence leak price fixing.

A extra advanced and attention-grabbing instance may look as follows:

func loadAllPages() {
  // solely fetch pages as soon as
  guard fetchPagesTask == nil else { return }

  fetchPagesTask = Job { [weak self] in
    guard let self else { return }

    var hasMorePages = true
    whereas hasMorePages && !Job.isCancelled {
      let web page = await fetchNextPage()
      hasMorePages = !web page.isLastPage
    }

    // we're executed, we may name loadAllPages once more to restart the loading course of
    fetchPagesTask = nil
  }
}

Let’s take away some noise from this operate so we are able to see the bits which can be really related as to whether or not now we have a reminiscence leak. I needed to indicate you the total instance that can assist you perceive the larger image of this code pattern…

 Job { [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 Job now we have a [weak self] seize and instantly we unwrap with a guard self. You already know this may not do what we wish it to. The Job will begin working instantly, and self can be held on to strongly till our activity ends. That mentioned, we do need our Job to finish if self is deallocated.

To realize this, we are able to really transfer our guard let self into the whereas loop:

Job { [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 subsequent 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 mounted 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 Job closures in Swift do not strictly want [weak self] as a result of the Job typically solely exists for a comparatively brief period of time. Should you discover that you just do wish to make it possible for the Job does not trigger reminiscence leaks, it is best to make it possible for the primary line in your Job is not guard let self else { return }. If that is the primary line in your Job, you are capturing a powerful reference to self as quickly because the Job begins working which often is sort of instantly.

As a substitute, unwrap self solely if you want it and ensure you solely preserve the unwrapped self round as brief as potential (for instance in a loop’s physique). You can additionally use self? to keep away from unwrapping altogether, that method you by no means seize a powerful reference to self. Lastly, you could possibly think about not capturing self in any respect. Should you can, seize solely the properties you want in order that you do not depend on all of self to stay round if you solely want elements of self.

Related Articles

LEAVE A REPLY

Please enter your comment!
Please enter your name here

[td_block_social_counter facebook="tagdiv" twitter="tagdivofficial" youtube="tagdiv" style="style8 td-social-boxed td-social-font-icons" tdc_css="eyJhbGwiOnsibWFyZ2luLWJvdHRvbSI6IjM4IiwiZGlzcGxheSI6IiJ9LCJwb3J0cmFpdCI6eyJtYXJnaW4tYm90dG9tIjoiMzAiLCJkaXNwbGF5IjoiIn0sInBvcnRyYWl0X21heF93aWR0aCI6MTAxOCwicG9ydHJhaXRfbWluX3dpZHRoIjo3Njh9" custom_title="Stay Connected" block_template_id="td_block_template_8" f_header_font_family="712" f_header_font_transform="uppercase" f_header_font_weight="500" f_header_font_size="17" border_color="#dd3333"]
- Advertisement -spot_img

Latest Articles