One of those difficult things for even experienced developers to understand is Automatic Reference Counting, or ARC. It is how Swift manages and conserves memory automatically.
Take a look at the Exercise file. I have a playground in this project with two classes. One is a pizza topping.
class PizzaTopping{ let name:String var onPizza: Pizza! init(name:String){ self.name = name } deinit { print("Deinitializing \(name)") } }
One is a Pizza
.
class Pizza{ let name:String var topping:PizzaTopping! init(name:String){ self.name = name } deinit{ print("Deinitializing \(name)") } }
Look at the PizzaTopping
class. It has two methods defined, an init
to initialize name, and deinint
.
deinit
is a method that executes just before the class is deallocated, and we’ll use it to see things disappear.
On the bottom, you’ll see I made a few toppings and pizzas in a do
block. I use a do block here to simulate what would happen inside a class or function for a playground without using one. I’ll explain why I did that shortly.
do{ // Initialize toppings let pepperoni:PizzaTopping = PizzaTopping(name: "Pepperoni") let mushroom:PizzaTopping = PizzaTopping(name:"Mushroom") // Initialize pizzas let pepperoniPizza:Pizza = Pizza(name: "Pepperoni Pizza") let mushroomPizza:Pizza = Pizza(name:"Mushroom Pizza") // Add toppings to pizzas mushroomPizza.topping = mushroom pepperoniPizza.topping = pepperoni }
If you run now, you’ll see in the console the chain of deinitializing.
Deinitializing Mushroom Pizza Deinitializing Pepperoni Pizza Deinitializing Mushroom Deinitializing Pepperoni
The Pizzas and then the toppings deinitialize. This is ARC in action. Each assignment is a reference to an area in memory. ARC counts the number of references. Where there are no more references, ARC assumes that the memory is unused and frees it up. We’ve got cases here where there is one reference. For example, pepperoniPizza
has one reference. At the end of the do
block, that value is discarded and I have zero references. When ARC finds zero references, it cleans out that memory, deallocating the space, and deinit
fires.
One place that changes is global variables. I used do
to keep all those variables as local. The default behavior in Playgrounds is a global variable. Let’s add a global above of the do
code.
var myPizza:Pizza! = Pizza(name: "Goat Cheese")
Run again.
Deinitializing Mushroom Pizza Deinitializing Pepperoni Pizza Deinitializing Mushroom Deinitializing Pepperoni
Goat Cheese doesn’t show as deallocated. Globals are not deallocated until the app is removed from memory.
Assign the mushroom pizza to my pizza.
myPizza = (mushroomPizza)!
Run again.
Deinitializing Goat Cheese Deinitializing Pepperoni Pizza Deinitializing Pepperoni
The Goat Cheese pizza deinitializes, the mushroom doesn’t, and the mushroom topping doesn’t. We changed the reference. Let’s go down the steps here carefully:
- We have a Global pizza Goat Cheese in
myPizza
- We assign the mushroomPizza to myPizza. It has a topping of mushroom. The Goat Cheese instance has zero references and deallocates.
- The do block ends.
PepperoniPizza
andpepperoni
have zero references. ARC deallocates these. - The global only closes when we close the playground, so there is a lifetime reference to
myPizza
.mushroomPizza
and the value in its propertytopping
ofmushroom
are assigned there, so they do not deallocate.
ARC can be fooled into not deallocating memory through a strong reference cycle. lets suppose I track what pizza has a topping in the Topping class with the property onPizza, for example add
pepperoni.onPizza = pepperoniPizza
Run again
Deinitializing Goat Cheese
,Pepperoni
doesn’t deinitialize. It has two strong references, the Pizza
in onPizza
recording the pizza it is on and the pepperoni
topping in PepperonipPizza
. The topping can’t deallocate until the pizza does and the pizza can’t deallocate until the topping does. When you have a lot of instances of this happening you’ll have an expansion of wasted memory known as a memory leak.
You can break this reference cycle by changing one of the two properties to weak
. I’ll change onPizza
in this case,
weak var onPizza:Pizza!
For a weak variable, Arc changes its value to nil
when it tries to clean up at the end of the block and then cleans it up. That’s why `onPizza` is optional in this code.
The strong reference cycle is broken, and it deallocates the memory of the weak variable. Run now and the pepperoni deallocates.
Deinitializing Goat Cheese Deinitializing Pepperoni Pizza Deinitializing Pepperoni
Of course, the best way to avoid this is to use searches of pizzas for their toppings rather than onPizza
. ARC does much of the work to keep memory clean, but in large codebases, it is too easy to create a memory leak by a strong reference cycle. Knowing how to avoid them and how to use weak
when you can’t are important skills.
The Whole Code
Here’s the playground code for this week’s tip. You’ll also find it on GitHub here
// // An exercise file for iOS Development Tips Weekly // by Steven Lipton (C)2018, All rights reserved // For videos go to http://bit.ly/TipsLinkedInLearning // For code go to http://bit.ly/AppPieGithub // import UIKit class PizzaTopping{ let name:String weak var onPizza: Pizza! init(name:String){ self.name = name } deinit { print("Deinitializing \(name)") } } class Pizza{ let name:String var topping:PizzaTopping! init(name:String){ self.name = name } deinit{ print("Deinitializing \(name)") } } var myPizza:Pizza = Pizza(name: "Goat Cheese") do{ // Initialize toppings let pepperoni:PizzaTopping = PizzaTopping(name: "Pepperoni") let mushroom:PizzaTopping = PizzaTopping(name:"Mushroom") // Initialize pizzas let pepperoniPizza:Pizza = Pizza(name: "Pepperoni Pizza") let mushroomPizza:Pizza = Pizza(name:"Mushroom Pizza") // Add toppings to pizzas mushroomPizza.topping = mushroom pepperoniPizza.topping = pepperoni myPizza = mushroomPizza pepperoni.onPizza = pepperoniPizza }
Leave a Reply