[weak self] in Swift Made Easy

In Swift, [weak self] creates a weak reference to self in a closure. This prevents memory leaks due to strong reference cycles.

However, to truly understand what this means, you need to understand the following concepts:

  • ARC
  • Strong reference
  • Strong reference cycle (retain cycle)
  • Weak reference

In this guide, you are going to take a thorough look at each of these concepts. Finally, you are going to learn how [weak self] works in a closure.

Old-School Memory Management in Swift

In the earlier days, it was the developer’s responsibility to handle the memory management of the application.

This meant when you created an object, you needed to manually allocate memory for it.

For example, if you wanted to create a Fruit object, you would first need to allocate a chunk of memory by:

Fruit.alloc()

And when you wanted to destroy this object, you would need to deallocate the memory eaten up by the object.

Fruit.release()

Imagine doing this kind of memory management in a big application where you have a ton of objects. It becomes infeasible. Sometimes you forget to release memory which causes memory leaks, and sometimes you prematurely release the memory which causes unexpected behavior.

To remedy this, Apple came up with Automatic Reference Counting (ARC), which is a built-in memory management system that allocates and deallocates memory automatically.

ARC – Automatic Reference Counting

ARC or Automatic Reference Counting in Swift is a built-in memory management feature.

ARC is like a housekeeper that constantly analyzes the state of your app. When the ARC sees that an object is no longer needed, it automatically deallocates the object from memory.

But how does ARC know when it should release the memory eaten up by objects?

ARC tracks the reference count of each object.

A reference count is a number that reveals “how many objects are using this object”. If this number goes to 0, ARC knows this object is not used anywhere so it can be released from memory.

A reference count is a number that increases by 1 whenever there is a strong reference to the object. And when the strong reference is removed, the count decreases by 1.

Strong References in Swift

By default, whenever you create a variable using the var keyword in Swift, you create a strong reference to an object.

Now, let’s see ARC and strong references in action.

For example:

var name = "Alice"

Under the hood, “Alice” is a String object that is strongly referenced by a variable called name. This increases the reference count of the “Alice” to 1.

Now, if you assign nil to the name variable, you break the strong reference to “Alice”.

name = nil

This drops the reference count of “Alice” to 0 because no variable is referencing it anymore. Behind the scenes, the ARC kicks in and releases the memory eaten up by “Alice”.

Now you have a basic understanding of how ARC works and what is a strong reference.

To actually verify that ARC is doing the housekeeping behind the scenes, you can write a deinitializer to a class.

The deinit() Method in Swift

One way to see the ARC in action is by running a piece of code right before the ARC deallocates the memory. This is possible using the deinit() method. This method runs right before ARC deallocates the memory taken by the object.

For example, let’s create a simple Fruit class with a deinitializer that displays a message when the memory is freed up:

class Fruit {
    var name = "Banana"

    deinit {
        print("Freeing up the Fruit")
    }
}

Now, let’s create a Fruit object and a variable called banana that points to it:

var banana: Fruit? = Fruit()

Now the reference count of the Fruit object is 1.

Now, let’s assign nil to banana:

banana = nil

As you learned, this drops the reference count of the Fruit object to 0 and triggers the ARC to clean it up. This is when the deinit() method executes and produces the following output into the console:

Freeing up the Fruit

This way you could verify that the ARC indeed did some housekeeping behind the scenes.

Using a deinitializer this way becomes useful in the next chapter.

Next, let’s take a look at a case where two objects strongly reference one another and prevent ARC from deallocating memory.

Retain Cycles in Swift

In Swift, the ARC deallocates the memory of an object whose reference drops to 0.

In the previous chapter, you saw an example of ARC in action when a reference was removed from an object.

But what happens if two objects strongly refer to one another?

This causes a strong reference cycle also known as a retain cycle.

A retain cycle prevents ARC from deallocating memory eaten up by the objects because the reference count of either object is always 1.

Let’s see an example of a strong reference cycle between two objects that reference one another. To do this, let’s create a class Pet and a class Owner:

class Pet {
    let name: String
    var owner: Owner?

    init(name: String) { self.name = name }
    
    deinit {
        print("Pet deallocated")
    }
}

class Owner {
    let name: String
    var pet: Pet?

    init(name: String) { self.name = name }
    
    deinit {
        print("Owner deallocated")
    }
}

If you take a look at the implementations, you can see that a Pet can have an Owner object and an Owner object can have a Pet.

In other words, a Pet object can strongly reference an Owner object that strongly references the Pet object.

Let’s create a strong reference cycle by initializing a Pet and an Owner object and making them point to one another:

var pet: Pet? = Pet(name: "Dog")
var owner: Owner? = Owner(name: "Alice")

pet!.owner = owner
owner!.pet = pet

Now, let’s assign nil to the pet and the owner. This should trigger the deinit() methods before ARC cleans up the objects…

pet = nil
owner = nil

However, this produces no output to the console!

The deinit() methods of the objects did not run. This is because the ARC could not deallocate the memory taken by the objects due to the retain cycle. The reference count of both the pet and the owner is always 1 as they reference one another.

This is problematic because it causes a memory leak. If you print the pet and owner objects, you can indeed see they are nil:

print(pet)
print(owner)

Output:

nil
nil

However, the ARC was unable to deallocate the space taken by these objects.

So somewhere in memory, the memory is still reserved to these objects even though it should not. This so-called memory leak can be critical because it can eat up all the memory reserved for the application.

Luckily, this kind of memory leaking can be solved by using weak references instead of strong ones.

Weak References in Swift

Strong references can cause retain cycles which cause memory leaks in your application. This is because a strong reference cycle prevents ARC from deallocating memory.

As you recall, a strong reference increases the reference count of an object by 1. And when the reference count is non-zero, ARC cannot deallocate the memory taken by the object.

To avoid retain cycles, you need to create a reference that does not increase the reference count so that the ARC can function properly.

This is where you can use a weak reference.

A weak reference is a reference type that does not increase the reference count. Thus the ARC can free up the space taken by the referenced object at any time.

To create a weak reference, use the weak keyword in front of a variable name.

Now, let’s go back to the problem in the previous chapter.

In the example, a Pet and an Owner object produced a strong reference cycle, where a Pet references to an Owner that references to the Pet.

To break the strong reference cycle, all you need to do is declare one of the references weak.

For example, let’s modify the Pet class such that the reference to an Owner object is weak:

class Pet {
    let name: String
    weak var owner: Owner?

    init(name: String) { self.name = name }
    
    deinit {
        print("Pet deallocated")
    }
}

class Owner {
    let name: String
    var pet: Pet?

    init(name: String) { self.name = name }
    
    deinit {
        print("Owner deallocated")
    }
}

Then, let’s re-run the experiment we did earlier:

var pet: Pet? = Pet(name: "Dog")
var owner: Owner? = Owner(name: "Alice")

pet!.owner = owner
owner!.pet = pet

pet = nil
owner = nil

Output:

Owner deallocated
Pet deallocated

As you can see, now the deinit() methods ran successfully. This means that the ARC was able to release the memory eaten up by these objects.

This is because:

pet!.owner = owner
owner!.pet = pet

Did not cause a strong reference cycle, because the pet.owner is a weak reference that does not increase the reference count of the owner.

So far you have learned that:

  • A strong reference is a variable that points to an object in Swift.
  • A Strong reference increases the reference count by 1.
  • ARC deallocates memory reserved for an object when its reference count drops to 0.
  • A retain cycle means object1 references to object2 that references to object1.
  • A retain cycle prevents reference count from dropping to 0. This causes memory leaks.
  • To prevent retain cycles, use weak references.

With everything you have learned so far, you can finally understand what [weak self] means when working with closures in Swift.

[weak self] in Swift

In Swift, [weak self] prevents closures from causing memory leaks in your application. This is because when you use [weak self], you tell the compiler to create a weak reference to self. In other words, the ARC can release self from memory when necessary.

Closures and Strong Reference Cycles

In Swift, closures capture their context. In other words, anything “mentioned” inside the closure will be strongly referenced by the closure.

If you have a closure inside a class that uses self, the closure will keep a strong reference to self as long as the closure lives in memory.

Now, imagine that self refers to a view controller and there is a closure that performs an operation without [weak self].

This creates a strong reference cycle between the closure and the view controller.

If you now pop the view controller off the navigation stack, it will be kept alive by the closure that strongly references it. In other words, the dismissed view controller continues to perform even though it was deleted. This can lead to unexpected behavior, such as exceptions or memory crashes.

To prevent this, use [weak self] in the closure. This makes the reference to self weak which allows ARC to deallocate it when necessary.

Syntactic Sugar

At this point, it is good to understand that [weak self] is just a syntactically convenient way to create a weak reference to self.

In other words, this piece of code:

let action = { [weak self] in  
  self?.doStuff()
}

Is functionally equivalent to this:

weak var self_ = self

let action = {
  self_?.doStuff() 
}

Next, let’s take a look at a concrete example of using [weak self] in Swift.

[weak self] Example in Swift

Let’s see a demonstrative example of a closure that creates a strong reference cycle.

Here is a StopWatch class:

import Foundation

class StopWatch {
	var elapsedTime: Int = 0
	var timer: Timer?
	
	func start() {
		timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { _ in
			self.elapsedTime += 1
		}
	}
}

The timer works with a closure that increments the elapsed time every second. To do this it has to access the elapsedTime property of the StopWatch class via self.

  • This creates a strong reference to self from the closure.
  • But the timer is also automatically strongly referenced by the StopWatch class (or self).

This means there is a retain cycle.

Now, let’s add another method called stop() to the StopWatch class where we stop the timer:

import Foundation

class StopWatch {
	var elapsedTime: Int = 0
	var timer: Timer?
	
	func start() {
		timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
			self?.elapsedTime += 1
		}
	}

    func stop() {
		timer?.invalidate()
		timer = nil
	}
}

When you call stop() on a StopWatch object, it indeed stops the timer. But due to the strong reference cycle introduced in the closure, the timer memory is not deallocated by ARC.

Thus, we need to find a way to break the strong reference cycle. As you learned, you can do this by creating a weak reference between the closure and self:

import Foundation

class StopWatch {
	var elapsedTime: Int = 0
	var timer: Timer?
	
	func start() {
		timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
			self?.elapsedTime += 1
		}
	}

    func stop() {
		timer?.invalidate()
		timer = nil
	}
}

Now you have successfully broken the strong reference cycle between the closure and self. Now the ARC can release the memory taken by the StopWatch.

If you take a look at the above piece of code, you can see that after using [weak self], accessing self happens with self?. This is because self is now an optional value.

Last but not least, let’s go through common ways to handle the optional self in the closure.

Weak References and Optionals

When using [weak self] in a closure, self becomes an optional value. This is simply because self could be nil any time due to the weak reference.

This means self needs to be accessed via self? inside the closure like this:

timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
	self?.elapsedTime += 1
}

However, one could argue it is annoying to do optional handling everywhere in the closure.

Luckily, there is another way to do it by checking if self is nil at the beginning.

Make Sure ‘self’ Is Not ‘nil’

An alternative way to treat the optional self inside the closure is by first checking if it’s nil or not:

timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] _ in
        guard let self = self else { return }
		self.elapsedTime += 1
}

You can reuse self keyword as seen above. This checks if the self is nil. If it’s not, the closure continues running and the self is accessible via the self keyword just like before. If the self is nil, the closure returns and stops running.

Now you know how to handle self as an optional value inside a closure.

But what if you are 100% sure that self is not nil?

There is a way to treat the weakly referenced self as a guaranteed non-nil by using the unowned keyword.

The [unowned self] in Swift

In Swift, a weak reference makes the variable optional. This is because the variable could be nil.

But you can also create a weak reference that does not make the variable optional. This is possible using the unowned keyword.

For example, you could use [unowned self] in a closure instead of [weak self].

But this is not a good idea. When using an unowned reference, you would have to be sure that the referenced object stays in memory as long as the object that refers to it. And this is rarely the case.

For example, in the StopWatch example, this would not work.

Conclusion

You can use [weak self] to prevent a closure causing memory leaks in your program.

To really understand what it means you learned the following concepts:

  • ARC
  • Strong reference
  • Retain cycle
  • Weak reference

Here is a short summary of everything you learned today.

Each Swift object has a reference count. This tells how many variables or objects are strongly referencing the object. If the reference count goes to 0, ARC or Automatic Reference Count deallocates the memory of that object.

Whenever you create a variable in Swift, you create a strong reference to an object. When you assign nil to the variable, you break the strong reference. Then the ARC deallocates the referenced object from memory.

var name = "Alice" // Name is a strong reference to "Alice" string object.

name = nil         // Now "Alice" has 0 strong references. Thus, ARC deallocates "Alice".

Sometimes you can have a situation where object1 strongly references object2, and object2 strongly references object1. This is called a strong reference cycle, or retain cycle. The reference count of both objects is 1, which means ARC cannot deallocate the memory when necessary.

pet!.owner = owner
owner!.pet = pet
Strong reference cycle vs Weak reference cycle

This strong reference cycle is easy to break, though. Make one of the references weak by using the weak keyword modifier.

weak var owner: Owner?

In Swift, a closure captures its context. This means it strongly references anything referred to inside of it. If you have a closure that belongs to a class, and you call self inside that closure, you create a strong reference cycle.

This is because:

  • The closure strongly references to self.
  • And self (the class) strongly references to the closure.

To break a strong reference cycle in a closure, use [weak self]. This makes the closure weakly reference self.

When you use [weak self], remember that self becomes optional. Thus, you need to handle optionals in a closure that weakly references self.

Thanks for reading!

Further Reading

50 Swift Interview Questions

Share this article with others:

Share on twitter
Share on linkedin
Share on facebook
Share on pinterest
Share on email

Learn these 10 tips to write professional code.

 

Make your coding life easier with these amazing command-line tools

 

Learn how to use latest advancements in AI to write faster.

 

Leave a Comment

Your email address will not be published.