Automatic Reference Counting (ARC) in class instances

An introduction to ARC

In Swift language memory management works out of the box and you don’t need even to think about it yourself.

In this article i’ll share the basic about ARC. ARC just works! It’s frees up the memory used by your class instances when their no longer needed.

By Apple words:

Every time you create a new instance of a class, ARC allocates a chunk of memory to store information about instance. Addictionally, when an instance is no longer needed, ARC frees up the memory used by that instance so that the memory can be used for other purposes insted.

Let’s practice!!!

Let’s see how this works with our previous music app idea example.

You have a class called Artist. We will play a little bit with code now.

class Artist {
	let name: String

	init(name: String) {
		self.name = name
		print("Artist \(name) is being initialized")
	}

	deinit {
		print("Artist \(name) is being deinitialized")
	}
}

We will now create an Artist instance and assign it to some variables.

var artist1: Artist?
var artist2: Artist?
var artist3: Artist?

artist1 = Artist(name: "Taylor Swift")

artist2 = artist1
artist3 = artist1

artist1 = nil
artist2 = nil

Because the instance in artist3 it wasn’t nil, Artist instance it’s never deallocated. You can check this by seeing the number of "Artist \(name) is being deinitialized" printed in the result in your console. And this happens because there is now a strong reference from reference1 to the new Artist instances. The third strong reference never was deallocated and so, the instance it’s broken. So you need to change the line to artist3 = nil in order to fix the last strong reference.

Now lets learn about new references than strong, weak and unowned, by understanding how it’s possible sometimes to has instance of a class than never gets to the point where it has zero strong references and this is known as strong reference cycle.

Here is where we defined some of our relationships between classes as weak or unowned insted of strong references.

A practical example about weak.

We have one class called Artist and other called Label.

class Artist {
	let name: String
	var label: Label?

	init(name: String) {
		self.name = name
		print("Artist \(name) is being initialized")
	}

	deinit {
		print("Artist \(name) is being deinitialized")
	}
}

The second class called Label.

class Label {
	let name: String
	weak var artist: AnArtist?

	init(name: String) {
		self.name = name
		print("Label \(name) is being initialized")
	}

	deinit {
		print("Label \(name) is being deinitialized")
	}
}

Let’s play with some initialization. Below is the result!

var artist: Artist?
var label: Label?

artist = Artist(name: "Taylor Swift")
label = Label(name: "Sonic Music")

artist!.label = label
label!.artist = artist

artist = nil
label = nil

As you can see, when we pass nil to both variables, the strong reference not drop to zero and the instance it’s not deallocated. Neither deinitializer was called and nothing it’s printed in console. This can cause memory leak and other problems to your apps. I see this happening a lot while people work with instances of UITableView delegate methods or when try to capture values inside Clocures. We will expand more about these topics in future articles.

So, how we break this?

weak weak weak!

In this case because we can have an Artist without a Label or Label without an Artist, it’s possible to use weakreference. Because the lifetime of Label can finish first than the Artist in this context, we use weak references.

We use weak references when the other instance has a shorter lifetime, this mean, when the other instance can be deallocated first.

In the previous example we just need to change our class by adding the weak reference before the Artist declaration.

weak var artist: Artist?

The Artist instance still has a strong reference to the Label instance, but the Label instance has now a weak reference to the Artist instance.

Now, if in our test we add artist = nil, we will see a different result in console and instances being deallocated. Try yourself a litle bit!

So when to use the unowned reference then?!

We use it when the other instance has the same lifetime or longer lifetime.

In the below quick example, you can see two classes and understand their relationship. An Artist can have zero or more Albums but an Album can’t exists without an Artist.

class Artist {
	let name: String
	var album: Album?

	init(name: String) {
		self.name = name
		print("Artist \(name) is being initialized")
	}

	deinit {
		print("Artist \(name) is being deinitialized")
	}
}
class Album {
	let title: String
	unowned let artist: Artist

	init(title: String, artist: Artist) {
		self.title = title
		self.artist = artist
		print("Album \(title), Artist \(artist.name) is being initialized")
	}

	deinit {
		print("Album \(title) is being deinitialized")
	}
}

This is a great scenario to use the unowned reference. Because an Album will always have an Artist, we use the unowned reference to avoid strong reference cycle in the defined property. You can just update the Album class with

unowned let artist: Artist

See some results and please try different inputs to test diferrent outputs to better understanding.

Always check wether the deinitializer was called or not.

var artist: Artist?
var album: Album?

artist = Artist(name: "Taylor Swift")
artist!.album = Album(title: "Fearless", artist: artist!)

artist = nil

That’s it.

Resources

While there is a lot to find on the internet about ARC, I mainly used the Apple Swift Book and it is very clear and complete.

  • https://docs.swift.org/swift-book/

I hope you enjoyed the post and if so, please follow me because more content will come soon. I’ll write more about memory management in the next articles.

Also consider following me on Twitter? 😬 @amarildulucas