if you are performing (for example) a network request inside a class, and in the completion of that request you reference a function that belongs to that class, you must pass [weak self] in, like this
This isn't quite true. When you create a closure in Swift, the variables that the closure references, or "closes over", are retained by default, to ensure that those objects are valid to use when the closure is called. This includes self, when self is referenced inside of the closure.
The typical retain cycle that you want to avoid requires two things:
- The closure retains self, and
- selfretains the closure back
The retain cycle happens if self holds on to the closure strongly, and the closure holds on to self strongly — by default ARC rules with no further intervention, neither object can be released (because something has retained it), so the memory will never be freed.
There are two ways to break this cycle:
- Explicitly break a link between the closure and - selfwhen you're done calling the closure, e.g. if- self.actionis a closure which references- self, assign- nilto- self.actiononce it's called, e.g.
 - self.action = { /* Strongly retaining `self`! */
    self.doSomething()
    // Explicitly break up the cycle.
    self.action = nil
}
 - This isn't usually applicable because it makes - self.actionone-shot, and you also have a retain cycle until you call- self.action(). Alternatively,
 
- Have either object not retain the other. Typically, this is done by deciding which object is the owner of the other in a parent-child relationship, and typically, - selfends up retaining the closure strongly, while the closure references- selfweakly via- weak self, to avoid retaining it
 
These rules are true regardless of what self is, and what the closure does: whether network calls, animation callbacks, etc.
With your original code, you only actually have a retain cycle if apiClient is a member of self, and holds on to the closure for the duration of the network request:
func performRequest() {
   apiClient.performRequest { [weak self] result in
      self?.handleResult(result)
   }
}
If the closure is actually dispatched elsewhere (e.g., apiClient does not retain the closure directly), then you don't actually need [weak self], because there was never a cycle to begin with!
The rules are exactly the same with Swift concurrency and Task:
- The closure you pass into a Taskto initialize it with retains the objects it references by default (unless you use[weak ...])
- Taskholds on to the closure for the duration of the task (i.e., while it's executing)
- You will have a retain cycle if selfholds on to theTaskfor the duration of the execution
In the case of function2(), the Task is spun up and dispatched asynchronously, but self does not hold on to the resulting Task object, which means that there's no need for [weak self]. If instead, function2() stored the created Task, then you would have a potential retain cycle which you'd need to break up:
class AsyncClass {
    var runningTask: Task?
    func function4() {
        // We retain `runningTask` by default.
        runningTask = Task {
            // Oops, the closure retains `self`!
            self.printSomething()
        }
    }
}
If you need to hold on to the task (e.g. so you can cancel it), you'll want to avoid having the task retain self back (Task { [weak self] ... }).