Understand ARC

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:

  1. We have a Global pizza Goat Cheese in myPizza
  2. We assign the mushroomPizza to myPizza. It has a topping of mushroom. The Goat Cheese instance has zero references and deallocates.
  3. The do block ends. PepperoniPizza and pepperoni have zero references. ARC deallocates these.
  4. The global only closes when we close the playground, so there is a lifetime reference to myPizza. mushroomPizza and the value in its property topping of mushroom 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 onPizzarecording 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

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: