avatarAvijit Nagare

Summary

The provided content discusses memory management in iOS Swift, detailing the transition from manual memory management in Objective-C to Automatic Reference Counting (ARC) in Swift, and how developers can prevent memory leaks using weak and unowned references.

Abstract

Memory management in iOS has evolved significantly with the introduction of Swift. Unlike Objective-C, which required manual memory management through retain and release calls, Swift utilizes ARC to automatically manage memory for class instances. The article explains the principles of ARC, how it tracks and releases memory, and the importance of understanding reference counting to avoid memory leaks. It also delves into the concepts of strong, weak, and unowned references, illustrating how developers can use them to break strong reference cycles and ensure proper memory deallocation. The article emphasizes the need for developers to be aware of the relationships between instances, such as parent-child or bidirectional relationships, to effectively manage memory and maintain performance in iOS applications.

Opinions

  • The author suggests that while ARC simplifies memory management, developers still need to be mindful of object relationships to prevent retain cycles.
  • The article conveys that understanding memory management is crucial for developing efficient and leak-free iOS apps.
  • It is implied that the use of weak and unowned references is a best practice in Swift to manage memory in cases where objects have different lifetimes or ownership.
  • The author emphasizes the importance of proper initialization and deinitialization patterns, particularly in the context of two-phase initialization in Sw

How Does Memory Management Work in iOS Swift?

Photo by Pixabay from Pexels

Non-members can read here.

Around Objective C

The developers who worked on Objective C language they might be missing bracket syntax []

Ex. Person *p = [[Person alloc] init];
    [p getFullName];

Memory management in Objective C.

Developers were responsible for manual memory management as there was no ARC(Automatic Reference Counting) below Xcode 5. If you allocate any object.

Ex. Person *p = [[Person alloc] init]; // initial retain count value 0.

Above allocation increases the retain count by 1. It reserved space in memory.

[p release];

On release call [p release] decrease retain count value by 1. Once retain count value becomes 0 then reserved space used to release/free from memory.

Same way if another pointer of Person *b is referencing to Person object p

 b = p // Strong hold from "b"

Now retain count for p becomes 2. As there were 2 strong hold to Person object from “p” and “b” in memory. You were responsible for releasing those strong hold to that Person object.

set b = nil // Retain count drops to 2 to 1
[p release] // Retain count drops to 1 to 0

So, now Person objects used to release from memory as strong hold from “b” was removed first and “p” also released.

You know history now. Let’s jump to Swift.

Memory management (ARC):

In Swift you don’t need to think more about ARC as it automatically frees up memory used by Classes. But in few cases, you need give some more information objects relationship like parent-child Reference counting applies to classes. Structures and Enumerations are value type to no need to worry about them.

ARC(Automatic Reference Counting):

When you create a Class object ARC allocates some memory to store that object information Ex. type, store value. If class objects are not needed (No strong hold), ARC frees up memory used by instance so that memory can be used by some other instances.

If ARC were to deallocate instances that are still in and you call the instance method, you will get an app crash. ARC does not deallocate class instances as long as any property, constant, variable refers to it.

Whenever you assign a class instance to any variable, constant or variable it creates a Strong reference to that class instance and does not allow deallocating due to this Strong hold.

class Animal {
  let name: String
  init(name: String) {
    self.name = name
    print("\(name) is being initialized")
  }
  deinit {
    print("\(name) is being deinitialized")
  }
}

{
  let tom = Animal(name: “Tom”)
} //Once object is created init method will print “
// Tom is being initialised”
// Same way it will print “Tom is being deinitialized”
// The braces {} here create scope for tom objet.

Let’s create some references:

var firstRef: Animal?
var secondRef: Animal?
var thirdRef: Animal?

These are optional declarations, so they are initialized with nil value.

firstRef = Animal(name: "Tom")
//Prints "Tom is being initialized" i.e. Initialization

Now, Animal reference is assigned to reference1 variable. There’s now a strong reference from “firstRef” to the Animal instance(Retain count =1). With this strong hold ARC will not deallocate it.

secondRef = firstRef 
//secondRef holds Animal instance with strong reference. Retain count increases to 2
thirdRef = firstRef 
// Again thirdRef holds Animal with strong reference. Now the Retain count increases to 3.

There are 3 strong hold/references “firstRef”, “SecondRef”, “ThirdRef” to single Animal (Tom) instance. Say, if you set nil to only “firstRef” and “ThirdRef” references but still “secondRef” has a strong hold to Animal (Tom) object and ARC won’t allow to free/deallocate Animal instance.

firstRef = nil // Retain count drops to 3 to 2
ThirdRef = nil // Retain count drops to 2 to 1

As you can see, retain count 1 because “secondRef” is still not set to “nil”. If you set “secondRef” to “nil”. Now retain count drops to 0 and ARC will deallocate Animal (Tom) object and print “Tom is being deinitialized”. Note: Retain count is mentioned just for understanding the purpose. ARC handles it internally.

Strong reference cycle in class instances:

There are times where 2 class instances hold each other with strong hold/references which never get deallocated, and this is called Strong reference cycle. To fix this issue you need to use a weak and unowned relationship between 2 instances.

Weak references:

Let’s continue with Animal example:

class Animal {
  let name: String
  var owner: Owner?
  init(name: String) {
    self.name = name
    print("\(name) animal is being initialized")
  }
  deinit {
     print("\(name) animal is being deinitialized")
  }
} 
// Later we will make the owner property weak to fix the retain cycle issue in Animal class.

Say, every Animal can’t have an Owner all the time. The Owner class has a smaller lifespan than Animal class.

class Owner {
   let name: String
   var pet: Animal?
   init(name: String) {
      self.name = name
      print("\(name) owner is being initialized")
    }
   deinit {
      print("\(name) owner is being deinitialized")
  }
}

You can create optional variable for Animal and Owner class.

var cat = Animal?
var rob = Owner?

These both objects are nil initially. You can give them some actual value as below:

cat = Animal(name: “Mau”)// cat variable has strong reference to Animal Mau.
rob = Owner(name: “Rob”)// rob variable has strong reference to Owner Rob.

Set some references between “cat” and “rob” instance “!” Is used to access instance property.

cat!.owner = rob
rob!.pet = cat

This creates a strong reference cycle between them. Now even you set “cat” and “rob” to nil you will not see any deinit method called from either increase.

cat = nil //deinit method not called for Mau instance
rob = nil //deinit method not called for Rob instance.

Hence, Animal and Owner instances still exist in memory. This is because an instance property cat?.owner and rob?.pet holding a strong reference cycle causes memory leak in your app.

Note: There could be a strong reference cycle when A to B and B to A relationship.

How to break this strong reference cycle?

To break a strong retain cycle you need to use class type properties weak and unowned. The declared weak and unowned reference doesn’t hold strong reference to other class instances. The property declared with weak reference has smaller lifespan than unowned. For weak reference, there may be a case where an instance is already deallocated, in those cases ARC sets a weak reference to nil. As weak references allow to set their value nil at runtime, so they are always declared as optional variables.

class Owner {
  let name: String
  weak var pet: Animal?
  init(name: String) {
    self.name = name
    print("\(name) owner is being initialized")
  }
  deinit {
    print("\(name) owner is being deinitialized")
  }
}

A class Animal instance to Owner instance has a strong reference but Owner instance to Animal instance has weak reference as “pet” property declared as weak in owner class.

cat (Animal) -> rob (Owner) has strong reference.

rob (Owner) -> cat (Animal) has weak reference.

If you set strong reference “cat” to “nil”. There is no other strong references Animal instance “Mau” so it will deallocate from memory.

cat = nil. // Mau animal is being deinitialized.

Unowned reference:

Unowned doesn’t hold with strong reference like a weak reference. Unowned used when other instance objects have the same or longer lifetime. Unowned always expect value to be there, so unowned never be an optional variable. If you try to access unowned property after deallocation, your app will crash. Ex.

class Hospital {
  var patientName: Patient?
  init() { }
}
class Patient {
  var admittedIn: Hospital //will mark it unowned later.
  init(hospital: Hospital) {
    self.admittedIn = hospital
  }
}

Consider the relationship between Hospital and Patient class is that Every hospital may or may not have patients, but every patient should be undergoing some treatment through hospital. The patient should associate with some hospital all time, so the Patient class takes the Hospital’s instance into its initializer. The Patient instance will always be in the hospital so you can declare “admittedIn” Hospital property as “unowned”.

Declare property unowned whose lifespan is longer than other class instances.

class Patient {
  unowned var admittedIn: Hospital
  init(hospital: Hospital) {
    self.admittedIn = hospital
  }
}
//Hospital has a longer lifespan than Patient.
var someHospital: Hospital?
//This is just a pointer to Hospital class. Actual Hospital object is not created yet into the memory. Initially someHospital will be nil. You can create Hospital instance as below:
someHospital = Hospital()
someHospital?.patientName = Patient(hospital: someHospital)
// Hospital has strong reference to Patient and Patient has an unowned reference to the Hospital.

As unowned doesn’t hold strong reference so if “someHospital” variable (strong reference) is set to nil, there is no other strong reference to Hospital instance, so Hospital instance gets deallocated. Moreover, there is no strong reference to Patient it deallocated as well.

Unowned optional reference:

Earlier you read as unowned has a longer lifespan and can’t be optional. Unowned optional is the next version of unowned. Swift still allows you to mark unowned property as optional and you need to check if an object instance has some value or not. Like unowned, unowned optional doesn’t keep a strong hold and doesn’t stop ARC from deallocating an instance. Only difference is that “unowned optional” can be nil. Ex.

unowned var admittedIn: Hospital?

You can make “admittedIn” property optional in the above example. But before using this property you need to check if “admittedIn” has some value or nil as this is unowned optional.

Unowned reference and implicitly unwrapped optional:

Consider School and Principal relationship. Every school has a principal and every principal belongs to the school.

class School {
  let name: String
  var currentPrincipal: Principal!
  init(name: String, principalName: String) {
    self.name = name
    self.currentPrincipal = Principal(name: principalName, school: self)
  }
}
class Principal {
  let name: String
  unowned let school: School
  init(name: String, school: School) {
    self.name = name
    self.school = school
  }
}

The “currentPrincipal” property force unwrapped using ! i.e. implicitly unwrapped. To initialize a School instance fully, “currentPrincipal” and “name” properties have to be initialized first. So we marked “currentPrincipal” with ! to initialize with nil and School initializer takes name value and assigns it to “name” property.

Now, self is ready to initialize the property below.

self.currentPrincipal = Principal(name: principalName, school: self)

If you didn’t mark “currentPrincipal” with ! and tried to initialize the Principal class as above you will get a compile time error. Two-Phase initialization (allows safe initialization and allows other classes to access after initialization) takes place here for “currentPrincipal” property. This is a nested kind of class initialization. School class (First) -> Principal class (Second). Now, you can create a School and Principal instance in a single line without strong reference.

var mySchool = School(name: "MIT", principalName: "Hary")
print("\(mySchool.name) is driven by principal Mr. \(mySchool.currentPrincipal.name)")
// Prints "MIT is driven by Mr. Hary"

Hope, this read will help you to write leak free and less CPU consuming iOS apps.

Do clap if you like and happy coding!

Stackademic 🎓

Thank you for reading until the end. Before you go:

iOS
Swift
Swiftui
iOS App Development
Memory Management
Recommended from ReadMedium