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 artist1
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 have instance of a class that 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 class
es 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: Artist?
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 Clocure
s. 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 weak
reference. 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 class
es and understand their relationship. An Artist
can have zero or more Album
s 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.
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
Note: Please, english isn't my native language, so feel free to send me any feedback about typos or anything.
Top comments (0)