• Swift: Avoiding Memory Leaks by Examples
  • Original post by Jaafar Barek
  • The Nuggets translation Project
  • Permanent link to this article: github.com/xitu/gold-m…
  • Translator: LoneyIsError
  • Proofreader: HearFishle

In Swift, automatic reference counting (ARC) is used to manage memory usage in iOS applications.

Each time a new instance of a class is created, ARC allocates a block of memory to store information about it and automatically frees that memory when the instance is no longer needed.

As a developer, you don’t need to do anything for memory management, except in three cases where you need to tell ARC more about the relationships between instances to avoid “circular references.”

In this article, we’ll focus on these three situations and look at practical examples of circular references and how to avoid them.

But first, what are circular references and why should we avoid them?


Circular reference:

Circular references are the case where two objects have strong references to each other and hold each other, and ARC is unable to free these objects from memory, resulting in a “memory leak.”

Memory leaks in an application are very dangerous because they affect the performance of the application and can cause a crash when the application runs out of memory.


Memory leaks can occur in three ways:

1- Strong references between two classes:

Suppose we have two classes (the Author class and the Book class) that refer directly to each other:

class Author {
    var name:String
    var book:Book
    
    init(name:String,book:Book) {
        self.name = name
        self.book = book
        print("Author Object was allocated in memory")
    }
    deinit {
        print("Author Object was de allocated")
    }
}

var author = Author(name:"John",book:Book())
author = nil
Copy the code
class Book {
    var name:String
    var author:Author
    
    init(name:String,author:Author) {
        self.name = name
        self.author = author
        print("Book object was allocated in memory")
    }
    deinit {
        print("Book Object was deallocated")
    }
}
var book = Book(name:"Swift",author:author)
book = nil
Copy the code

In theory, since both objects are set to nil, it should print that both objects are allocated and then both objects are destroyed, but it prints the following:

Author Object was allocated in memory
Book object was allocated in memory
Copy the code

As you can see, the two objects are not freed from memory because a circular reference occurs when two objects have strong references to each other.

To solve this problem, we can declare weak or undirected references as follows:

class Author { var name:String weak var book:Book? // The book object needs to be declared as weak optional init(name:String,book: book?). { self.name = name self.book = bookprint("Author Object was allocated in memory")
    }
    deinit {
        print("Author Object was deallocated")}}Copy the code

This time both objects will be released and the console will print the following:

Author Object was allocated in memory
Book object was allocated in memory
Author Object was deallocated
Book Object was deallocated
Copy the code

The problem is solved, ARC can clean up a block of memory by making one of the references weak to free the object, but what are weak and unowned references? According to Apple’s documentation:

A weak reference

A weak reference is a reference that does not enforce retention of instances it refers to, and therefore does not prevent ARC from processing those instances. This prevents the reference from becoming part of a strong reference loop. You can flag weak references by placing the weak keyword before an attribute or variable declaration.

No primary reference

Like weak citation, an ownerless reference does not maintain a strong reference to the instance it references. However, unlike weak references, an ownerless reference is required when another instance has the same lifetime or longer. You can flag an undirected reference by placing the unowned keyword before a property or variable declaration.


2-class agreement relationship:

Another cause of memory leaks can be the close relationship between protocols and classes. In the following example, we will take a real scenario where we have a TablViewController class and a TableViewCell class, and when the user presses a button in the TableViewCell, It should delegate this action to the TablViewController as follows:

@objc protocol TableViewCellDelegate { func onAlertButtonPressed(cell:UITableViewCell) } class TableViewCell: UITableViewCell { var delegate:TableViewCellDelegate? @IBAction func onAlertButtonPressed(_ sender: UIButton) { delegate? .onAlertButtonPressed(cell: self) } }Copy the code
class TableViewController: UITableViewController {

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TableViewCell".for: indexPath) as! TableViewCell
        cell.delegate = self
        return cell
    }
 
    deinit {
        print("TableViewController is deallocated")
    }

}
extension TableViewController: TableViewCellDelegate {
    func onAlertButtonPressed(cell: UITableViewCell) {
        if let row = tableView.indexPath(for: cell)? .row {print("cell selected at row: \(row)")
        }
        dismiss(animated: true, completion: nil)
    }
}
Copy the code

Normally, when we close the TableViewController, ARC would call the deinit method and print “TableViewController is deallocated” in the console, but in this case, Because TableViewCellDelegate and TableViewController have strong references to each other, they are never released from memory.

To solve this problem, we can simply resize the TableViewCell class to look like this:

@objc protocol TableViewCellDelegate { func onAlertButtonPressed(cell:UITableViewCell) } class TableViewCell: UITableViewCell { weak var delegate:TableViewCellDelegate? @IBAction func onAlertButtonPressed(_ sender: UIButton) { delegate? .onAlertButtonPressed(cell: self) } }Copy the code

Closing the TableViewController this time can be seen in the console:

TableViewController is deallocated
Copy the code

3- Strong circular references to closures:

Suppose we have the following ViewController:

class ViewController: UIViewController {

    var closure : (() -> ()) = { }
  
    override func viewDidLoad() {
        super.viewDidLoad()
        closure = {
            self.view.backgroundColor = .red
        }
    }
    deinit {
        print("ViewController was deallocated")}}Copy the code

Try closing the ViewController, deinit will never be executed. This is because the closure captures a strong reference to the ViewController. To solve this problem, we need to use the weak or unowned modified self in the closure, as follows:

class ViewController: UIViewController {

    var closure : (() -> ()) = { }
  
    override func viewDidLoad() {
        super.viewDidLoad()
        closure = { [unowned self] in
            self.view.backgroundColor = .red
        }
    }
    deinit {
        print("ViewController was deallocated")}}Copy the code

When you close the ViewController this time the console will print:

ClosureViewController was deallocated
Copy the code

conclusion

There is no doubt that ARC is great for application memory management, and all we developers need to do is watch out for strong references between classes, between classes and protocols, and between internal closures, and avoid circular references by declaring weak or unowned.


Some important references to ARC:

  • Apple Official Documents
  • Raywenderlich’s article on ARC.

If you find any mistakes in your translation or other areas that need to be improved, you are welcome to the Nuggets Translation Program to revise and PR your translation, and you can also get the corresponding reward points. The permanent link to this article at the beginning of this article is the MarkDown link to this article on GitHub.


The Nuggets Translation Project is a community that translates quality Internet technical articles from English sharing articles on nuggets. The content covers Android, iOS, front-end, back-end, blockchain, products, design, artificial intelligence and other fields. If you want to see more high-quality translation, please continue to pay attention to the Translation plan of Digging Gold, the official Weibo, Zhihu column.