An in depth look at memory management for Swift

  • 2020-05-27 07:17:04
  • OfStack

preface

The LLVM compiler is good: Swift's memory management is almost entirely taken care of by the LLVM compiler, with the exception of the reference loop.

Swift automatically manages memory, which means that we no longer have to worry about memory requests and allocations. When we create an object via initialization, Swift manages and allocates memory for us. The principle of freeing follows the rule of automatic reference counting (ARC) : when an object has no references, its memory is automatically reclaimed. This mechanism greatly simplifies our coding by simply ensuring that references are left blank at appropriate times (such as out of scope, or manually set to nil, etc.) to ensure that memory usage does not become a problem.

However, all automatic reference counting mechanisms have one limitation that in theory cannot be bypassed, which is the case of circular references (retain cycle).

What is the reference loop problem

Swift USES the ARC (automatic reference counting) method to manage memory for reference types.

In Swift, when you declare a variable of a reference type and give it a negative value, you create a strong reference to that object, and the reference count of that object is added by 1. If two objects strongly reference each other, this will result in a reference loop. Once a reference loop 1 occurs, the associated object cannot be released, resulting in a memory leak.

Refer to scenarios and solutions where the loop problem occurs

In Swift, class objects and closures are passed by reference, so a reference loop occurs in the following scenarios:

Class objects strongly reference each other

When two objects reference each other, a reference loop is formed.


class Letter { 
 let addressedTo: String 
 var mailbox : MailBox? 

 init( addressedTo: String) {
 self. addressedTo = addressedTo 
 } 

 deinit { 
 printl(" The letter addressed to \(addressedTo) is being discarded") 
 } 
}

class MailBox { 
 let poNumber: Int 
 var letter : Letter? 

 init( poNumber: Int) {
 self. poNumber = poNumber 
 } 

 deinit { 
 print(" P.O Box \(poNumber is going away)") 
 } 
}

The Letter class strongly references the MailBox class object, and the MailBox class strongly references the Letter class object to form a reference loop.

Solution: strong references can be removed by adding the weak keyword (weak reference) when declaring an object. For example, when an letter object is declared as weak, the reference count of the mailbox object does not increase by 1, thereby dereferencing the reference loop. General 1 declares objects that logically belong to another 1 object as weak objects. Such as:


weak var letter : Letter?

A closure refers to an object that contains itself

A closure that references an object that contains itself also causes a reference loop.


class MailChecker { 
 let mailbox: MailBox 
 let letter: Letter 

 lazy var whoseMail: () -> String = { 
 return "Letter is addressed to \(self. letter.addressedTo)"
 }

 init(name: String) { 
 self. mailbox = MailBox( poNumber: 311) 
 self. letter = Letter( addressedTo: name) 
 } 

 deinit { 
 println(" class is being deintialized")
 }
}

The whoseMail closure in the sample code USES self to reference the MailChecker object that contains itself, which in turn owns the MailChecker object, which in turn owns the MailChecker object, leading to a reference loop.

Solution: at this point you can de-reference the loop by adding [unowned self] to let Swift know that the self object should not be retained. Change the closure to:


lazy var whoseMail: () -> String = { [unowned self] in
 return "Letter is addressed to \(self. letter.addressedTo)"
}

Note: the codes are taken from Boisy G. Pitre Swift basic tutorial

conclusion


Related articles: