When do we use [weak self] [unowned] [self]

Explaining weak and strong variables is better with an example. Let’s start with two classes Driver and Car. Don’t use Playgrounds to test this code. deinit doesn’t get called in playgrounds

class Car {
	var name : String
	var drivenBy : Driver?
	init(_ name: String) {
		print("Initializing...",String(describing: type(of: self)))
		self.name = name
	}
	deinit {
		print("Deinitializing...",String(describing: type(of: self)))
	}
}

class Driver {
	var name : String?
	var carName : Car?
	init(_ name: String) {
		print("Initializing...",String(describing: type(of: self)))
		self.name = name
	}
	deinit {
		print("Deinitializing...",String(describing: type(of: self)))
	}
}

class ViewController: UIViewController {

	override func viewDidLoad() {
		super.viewDidLoad()

		var carObj : Car?
		carObj = Car("Mazda")
		
		var driverObj : Driver?
		driverObj = Driver("Mark")
		
		carObj!.drivenBy = driverObj
		driverObj!.carName = carObj
		
		carObj = nil
		driverObj = nil
	}
}

Run the code, you should see the class getting initialized, but never getting de-initialized. Let’s dig into why this is not happening.

Initializing... Car
Initializing... Driver

In ViewDidLoad, we are assigning the driver object to the car object and vice versa as shown here

carObj!.drivenBy = driverObj
driverObj!.carName = carObj

There is a retain cycle formed here, with each class referring to a strong variable of the other class. Hence even if the class wants to de-initialize it’s not possible because of the strong reference cycle. In order to fix this retain cycle, you would need to make one of the variables as weak.

class Driver {
	var name : String?
	weak var carName : Car?
	init(_ name: String) {
		print("Initializing...",String(describing: type(of: self)))
		self.name = name
	}
	deinit {
		print("Deinitializing...",String(describing: type(of: self)))
	}
}
Initializing... Car
Initializing... Driver
Deinitializing... Car
Deinitializing... Driver

Here you go. The deinit is getting called now. This was a simple use case scenario between two classes. Let’s move on to closures.

Let’s consider you want to print the ratings of a driver. The ratings are stored on a server. You retrieve the ratings and print it out. I am adding a delay to show this.

class Driver {
	var name : String?
	weak var carName : Car?
	var ratings : [Car] = []
	init(_ name: String) {
		print("Initializing...",String(describing: type(of: self)), name)
		self.name = name
	}
	deinit {
		print("Deinitializing...",String(describing: type(of: self)), name ?? "")
	}
	
	func printRatings() {
		DispatchQueue.main.asyncAfter( deadline: .now() + 1) {
			var carObj : Car?
			carObj = Car("Closure_Car")
			self.ratings.append(carObj!)
			print("Ratings for this driver: \(String(describing: self.ratings.count))")
		}
	}
}
Initializing... Car Mazda
Initializing... Driver Mark
Deinitializing... Car Mazda
Initializing... Car Closure_Car
Ratings for this driver: 1
Deinitializing... Driver Mark
Deinitializing... Car Closure_Car

Everything works perfectly. The deinit is getting called correctly. In the next scenario, let’s make printRatings to take a car object and add it to the ratings array. Let’s see what happens.

class Driver {
	var name : String?
	weak var carName : Car?
	var ratings : [Car] = []
	init(_ name: String) {
		print("Initializing...",String(describing: type(of: self)), name)
		self.name = name
	}
	deinit {
		print("Deinitializing...",String(describing: type(of: self)), name ?? "")
	}
	
	func printRatings(drivenCar : Car) {
		DispatchQueue.main.asyncAfter( deadline: .now() + 1) { [weak self] in
			self?.ratings.append(drivenCar)
			print("Ratings for this driver: \(String(describing: self?.ratings.count))")
		}
	}
}

class ViewController: UIViewController {

	override func viewDidLoad() {
		super.viewDidLoad()

		var carObj : Car?
		carObj = Car("Mazda")
		
		var driverObj : Driver?
		driverObj = Driver("Mark")
		
		carObj!.drivenBy = driverObj
		driverObj!.carName = carObj

		driverObj?.printRatings(drivenCar: carObj!)
	
		driverObj = nil
		carObj = nil
	}
}
Initializing... Car Mazda
Initializing... Driver Mark
Ratings for this driver: Optional(1)

You would have thought this worked perfectly, if only the deinit methods weren’t there. Now you know there is something wrong here. We once again have a retain cycle. Let’s fix this.
If you notice I am passing in carObj to the closure. This has already been assigned to driverObj!.carName = carObj. The driveObj holds a strong reference to this. This won’t be released and hence deinit won’t be called. To fix this, all you need to do is make the drivenBy property as weak in the class Car.

class Car {
	var name : String
	weak var drivenBy : Driver?
	init(_ name: String) {
		print("Initializing...",String(describing: type(of: self)), name)
		self.name = name
	}
	deinit {
		print("Deinitializing...",String(describing: type(of: self)), name)
	}
}

Now run this code.

Initializing... Car Mazda
Initializing... Driver Mark
Deinitializing... Driver Mark
Ratings for this driver: nil
Deinitializing... Car Mazda

Still something wrong here. Deinit gets called, but the ratings is showing as nil. We should not be seeing this. Let’s fix this. Replace the printRatings method with the code below. My fix was to not use [weak self].
If you used [weak self], it automatically was being set to nil, when you were making driverObj = nil.

func printRatings(drivenCar : Car) {
	DispatchQueue.main.asyncAfter( deadline: .now() + 1) {
		self.ratings.append(drivenCar)
		print("Ratings for this driver: \(String(describing: self.ratings.count))")
	}
}

If you run the code now, you will get the output as shown below

Initializing... Car Mazda
Initializing... Driver Mark
Ratings for this driver: 1
Deinitializing... Driver Mark
Deinitializing... Car Mazda

Moving on to using weak self in closures. I will explain. Let’s consider, you have a closure as property and you pass it around. e.g. completion handlers. During this time, closure will capture a strong reference to self, and hence it’s better to pass it as a weak property.We can better understand this with the help of the example below.

class Driver {
	var name : String?
	weak var carName : Car?
	var ratings : [Car] = []
	var ratingCompletionHandler : ((_ result: Bool, _ car: Car) -> ())?
	init(_ name: String) {
		print("Initializing...",String(describing: type(of: self)), name)
		self.name = name
		
		ratingCompletionHandler = { result, car in
		}
	}
	deinit {
		print("Deinitializing...",String(describing: type(of: self)), name ?? "")
	}
	
	func printRatings(drivenCar : Car) {
		DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
			self.ratingCompletionHandler?(true, drivenCar)
		}
	}
}

class ViewController: UIViewController {

	override func viewDidLoad() {
		super.viewDidLoad()

		var carObj : Car?
		carObj = Car("Mazda")
		
		var driverObj : Driver?
		driverObj = Driver("Mark")
		
		carObj!.drivenBy = driverObj
		driverObj!.carName = carObj

		driverObj?.printRatings(drivenCar: carObj!)
	
		driverObj = nil
		carObj = nil
	}
}

Everything is initialized and de-initialized perfectly as shown below

Initializing... Car Mazda
Initializing... Driver Mark
Deinitializing... Driver Mark
Deinitializing... Car Mazda

Let’s go ahead and change the completion handler to print out the ratings

ratingCompletionHandler = { result, car in
	self.ratings.append(car)
	print("(completion handler)Ratings for this driver: \(String(describing: self.ratings.count))")
}
Initializing... Car Mazda
Initializing... Driver Mark
(completion handler)Ratings for this driver: 1

Now you are back to the same point, the values are printed out correctly, but the objects are never de-initialized. Closures can strongly capture, or close over, any constants or variables from the context in which they are defined. For example, if you use self inside a closure, the closure scope will maintain a strong reference to self for the duration of the scope’s life. If self also happens to keep a reference to this closure (in order to call it at some point in the future), you will end up with a strong reference cycle. Let’s fix this. [weak self] is the magic keyword here.

ratingCompletionHandler = { [weak self] result, car in
	self?.ratings.append(car)
	print("(completion handler)Ratings for this driver: \(String(describing: self?.ratings.count))")
}

Voila, everything works as usual. The power of weak.

Initializing... Car Mazda
Initializing... Driver Mark
(completion handler)Ratings for this driver: Optional(1)
Deinitializing... Driver Mark
Deinitializing... Car Mazda

I got a helpful flowchart from this website to determine if you need to [weak self] https://medium.com/flawless-app-stories/you-dont-always-need-weak-self-a778bec505ef

Unowned self vs weak self vs self
Unowned and weak are similar in the sense both don’t have ownership over the variable. Unowned can never be nil/optional. It will crash 100%. Use owned when you 100% it can never be nil.
Just for fun, replace weak self with unowned in the above code, will give you the same result.

ratingCompletionHandler = { [unowned self] result, car in
        self.ratings.append(car)
	print("(completion handler)Ratings for this driver: \(String(describing: self.ratings.count))")
		}

Leave a Reply

Your email address will not be published. Required fields are marked *