How ARC Works in Swift?

ARC, which stands for Automatic Reference Counting, is a memory management feature in the Swift programming language. It automatically tracks and manages the memory used by your app to ensure that objects that are no longer needed are deallocated, freeing up memory resources.
Official Apple documentation you can read on Apple Developer.
What we’ll know from that article:
- What is ARC. How it works?
- Functions of ARC
- Reference Counting
- Strong References
- Reference counting operations
- Retain Cycles(weak and unowned references)
- Strong reference cycle between class instances
- Strong reference cycle for closures
- Capture list
- Autorelease Pools
- Example of ARC
What is ARC. How ARC Works?
ARC — automatic reference counting. ARC automatically frees up memory used by class when instance haven’t strong references.
Reference counting aplied only for instances of classes.
Structures and enumerations are value types and not stored and passed by reference. There are stored on stack.
Functions of ARC
- ARC allocates a chunk of memory to store the information each and every time when a new class instance is created by init().
- Information about the instance type and its values are stored in memory.
- When the class instance is no longer needed it automatically frees the memory space by deinit() for further class instance storage and retrieval.
- ARC keeps in track of currently referring class instances properties, constants and variables so that deinit() is applied only to those unused instances.
- ARC maintains a ‘strong reference’ to those class instance property, constants and variables to restrict deallocation when the class instance is currently in use.
Here’s a brief overview of how ARC works in Swift:
Reference Counting:
Every time you create a new instance of a class in Swift, the memory is allocated to hold that instance, and its reference count is set to 1.
Strong References:
By default, Swift uses strong references when you create a new instance of a class and assign it to a property, constant, or variable. A strong reference increases the reference count of the instance, keeping it in memory.
Reference Counting Operations:
- Incrementing the Count: When a new strong reference is created (e.g., assigning an instance to a variable), the reference count is increased by 1.
- Decrementing the Count: When a strong reference goes out of scope, is reassigned, or set to nil, the reference count is decreased by 1.
- Deallocation: When the reference count reaches 0, Swift deallocates the memory occupied by the instance, releasing its resources.
Retain Cycles:
It’s essential to avoid strong reference cycles where two or more instances hold strong references to each other. This prevents the reference count from reaching 0, leading to memory leaks. To prevent cycles, you can use weak or unowned references.
- Weak References
- Unowned References
Weak References
weak
: A weak reference does not increase the reference count. If the instance being referenced is deallocated, the weak reference is automatically set to nil.
Example of weak reference:
class module {
let name: String
init(name: String) { self.name = name }
var sub: submodule?
deinit { print("\(name) Is The Main Module") }
}
class submodule {
let number: Int
init(number: Int) { self.number = number }
weak var topic: module?
deinit { print("Sub Module with its topic number is \(number)") }
}
var toc: module?
var list: submodule?
toc = module(name: "ARC")
list = submodule(number: 4)
toc!.sub = list
list!.topic = toc
toc = nil
list = nil
When we run the above program using playground, we get the following result −
ARC Is The Main Module
Sub Module with its topic number is 4
Note from Apple:
Property observers aren’t called when ARC sets a weak reference to
nil
Unowned References
unowned
: An unowned reference is similar to weak but assumes that the reference will always have a valid value and will not be nil during its lifetime. If the instance being referenced is deallocated, accessing the unowned reference will cause a runtime error.
Example of Unowned reference:
class student {
let name: String
var section: marks?
init(name: String) {
self.name = name
}
deinit { print("\(name)") }
}
class marks {
let marks: Int
unowned let stname: student
init(marks: Int, stname: student) {
self.marks = marks
self.stname = stname
}
deinit { print("Marks Obtained by the student is \(marks)") }
}
var module: student?
module = student(name: "ARC")
module!.section = marks(marks: 98, stname: module!)
module = nil
When we run the above program using playground, we get the following result −
ARC
Marks Obtained by the student is 98
IMPORTANT from Apple:
Use an unowned reference only when you are sure that the reference alwaysrefers to an instance that has not been deallocated.
If you try to access the value of an unowned reference after that instance has been deallocated, you’ll get a runtime error.
Strong reference cycle between class instances
Situation when two class instances hold a strong reference to each other, such that each instance keeps the other alive names “strong reference cycle”.
When it’s happens an instance of a class never gets to a point where it has zero strong references because it has one more has cycled reference.
class studmarks {
let name: String
var stud: student?
init (name: String) {
print("Initializing: \(name)")
self.name = name
}
deinit {
print("Deallocating: \(self.name)")
}
}
class student {
let name: String
var strname: studmarks?
init (name: String) {
print("Initializing: \(name)")
self.name = name
}
deinit {
print("Deallocating: \(self.name)")
}
}
var shiba: studmarks?
var mari: student?
shiba = studmarks(name: "Swift 4")
mari = student(name: "ARC")
shiba!.stud = mari
mari!.strname = shiba
When we run the above program using playground, we get the following result −
Initializing: Swift 4
Initializing: ARC
Strong Reference Cycles for Closures
When we assign a closure to the class instance property and to the body of the closure to capture particular instance strong reference cycle can occur. Strong reference to the closure is defined by ‘self.someProperty’ or ‘self.someMethod()’. Strong reference cycles are used as reference types for the closures.
class HTMLElement {
let samplename: String
let text: String?
lazy var asHTML: () -> String = {
if let text = self.text {
return "<\(self.samplename)>\(text)</\(self.samplename)>"
} else {
return "<\(self.samplename) />"
}
}
init(samplename: String, text: String? = nil) {
self.samplename = samplename
self.text = text
}
deinit {
print("\(samplename) is being deinitialized")
}
}
var paragraph: HTMLElement? = HTMLElement(samplename: "p", text: "Welcome to Closure SRC")
print(paragraph!.asHTML())
When we run the above program using playground, we get the following result −
<p>Welcome to Closure SRC</p>
Capture list
That construction allows us using weak/unowned references in closure.
lazy var loginCompletion: (Bool) -> Void = { [weak self] success in
if let `self` = self, success {
self.navigationController?.popToRootViewController(animated: true)
}
}
/*
[weak self] is closure capture list. It can be added all parameters what you need.
i.e. [weak self, unowned delegate]
*/
Autorelease Pools
Autorelease pool blocks allow objects to relinquish ownership without being deallocated immediately. Typically, you don’t need to create your own autorelease pool blocks, but there are some situations in which either you must — such as when spawning a secondary thread — or it is beneficial to do so — such as when writing a loop that creates many temporary objects.
In Objective-C, autorelease pool blocks are marked using @autoreleasepool
. In Swift, you can use the autoreleasepool(_:)
function to execute a closure within an autorelease pool block.
// MARK: - Autorelease pool
// Reduce device memory usage
for i in 0...1000 {
autoreleasepool {
let image = UIImage(named: "\(i)")
print("image: \(image?.size)")
}
}
Example:
Here’s a simple example of ARC in Swift:
class Person {
var name: String
var apartment: Apartment?
init(name: String) {
self.name = name
print("\(name) is being initialized.")
}
deinit {
print("\(name) is being deinitialized.")
}
}
class Apartment {
var unit: String
weak var tenant: Person?
init(unit: String) {
self.unit = unit
print("Apartment \(unit) is being initialized.")
}
deinit {
print("Apartment \(unit) is being deinitialized.")
}
}
var john: Person?
var unit4A: Apartment?
john = Person(name: "John Doe")
unit4A = Apartment(unit: "4A")
john!.apartment = unit4A
unit4A!.tenant = john
// Since the references are strong, both instances are kept in memory.
john = nil
unit4A = nil
// When setting the references to nil, the reference counts are decremented, and the instances are deallocated.
In this example, john
and unit4A
hold strong references to each other, but when they are set to nil, the reference counts decrease, and the instances are deallocated. The deinit
methods are called when deallocation occurs.
If you enjoyed reading this post, please share and give claps so others can find it👏🏻👏🏻👏🏻👏🏻👏🏻
You can also connect with me on📲
You can have a look at my code from down below👇🏻
Find it a good read?
If you have any comments, questions, or recommendations, feel free to post them in the comment section below💬
💁🏻♀️Happy coding!
Thanks😊
Follow Swiftfy for more updates!