avatarBaljit Kaur

Summary

The provided web content explains the concept of Automatic Reference Counting (ARC) in Swift, detailing its mechanisms, benefits, and potential pitfalls such as retain cycles, along with strategies to manage memory effectively using weak and unowned references.

Abstract

ARC is a memory management feature in Swift that automatically manages the lifecycle of class instances by incrementing and decrementing reference counts. It ensures that memory is allocated when an instance is created and deallocated when it is no longer needed. The article discusses strong references, reference counting operations, and the importance of avoiding retain cycles, which can lead to memory leaks. To prevent retain cycles, Swift provides weak and unowned references, which do not increase the reference count. The article also covers the use of capture lists in closures to manage memory and the role of autorelease pools in controlling the lifetimes of temporary objects. Examples are provided to illustrate ARC in action, including how to handle strong reference cycles between class instances and closures.

Opinions

  • The author emphasizes the necessity of understanding ARC for Swift developers to effectively manage memory and prevent memory leaks.
  • The use of weak and unowned references is presented as a critical strategy for breaking strong reference cycles, with the author suggesting that developers should choose between them based on the lifecycle of the involved instances.
  • The article implies that while ARC simplifies memory management, developers must still be vigilant about reference cycles, especially when dealing with closures and class instances.
  • The inclusion of code examples and the encouragement to follow the author on social media and professional networks suggest that the author values practical learning and community engagement.
  • The author's mention of autorelease pools indicates an understanding of more advanced memory management scenarios, highlighting the importance of this knowledge for optimizing performance in resource-intensive operations.

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📲

LinkedIn

You can have a look at my code from down below👇🏻

GitHub

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!

iOS
iOS App Development
iOS Development
iOS Apps
Swift
Recommended from ReadMedium