Understanding memory management is an important part of developing great apps in Swift for iOS and macOS. This post presents an overview of Automatic Reference Counting and the different types of memory references in Swift:

  1. Automatic Reference Counting (ARC) in Swift
  2. Strong References in Swift
  3. Retain Cycles and Memory Leaks
  4. Weak References in Swift
  5. Unowned References in Swift
  6. Do UIView Animation Blocks Require Weak or Unowned Self?

The examples of memory management in this post will reference the following types:

class API {
    // Strong reference to completion, a block
    var completion: ((Data?, Error?) -> Void) = { (_,_) in }
}

Automatic Reference Counting (ARC) in Swift

Automatic Reference Counting is a memory management method Swift uses to determine when objects should be deallocated. Each object has a reference count, and when the reference count reaches 0 the object is deallocated.

An object’s reference count is increased by 1 when a strong reference is assigned to that object. An object’s reference count is decreased by 1 when a strong reference is removed from that object. For example:

class Example {
    var exampleView: UIView
}

// An instance of MyView is initialized. The variable
// view is a strong reference and therefore the
// reference count of the MyView instance is 1
var view = MyView()

// An instance of Example is initialized. The variable 
// exampleView is a strong reference. Therefore
// the reference count of the MyView instance is 2
var example = Example(exampleView: view)

// The variable view is reassigned and releases
// the strong reference to MyView. The reference
// count of the MyView instance is now 1
view = UITextField()

// The variable example is reassigned and releases
// the previous Example object. This also releases the
// strong reference from Example to MyView. The reference
// count of the MyView instance is now 0, and the MyView 
// instance is deallocated
example = Example(exampleView: UITextField())

For more information about Automatic Reference Counting, check out an overview about Automatic Reference Counting by swift.org.

Strong References in Swift

Strong references in Swift increment the reference count by 1. By default, references in Swift are strong meaning using var without a modifier creates a strong reference.

class HomeVC: UIViewController {  
    // Creates a strong reference to an API instance. When 
    // a HomeVC instance is deallocated, HomeVC will 
    // decrease the reference count of the API instance by 1
    let api = API()

    // Placeholder used in the following examples
    func hideLoadingIndicator() {}
}

Retain Cycles and Memory Leaks

A retain cycle occurs when a series of instances all retain strong references to each other. This means that all the instances in the retain cycle will never be deallocated. In other words, a memory leak occurs when memory cannot be deallocated because of a retain cycle.

Example Retain Cycle in Swift

extension HomeVC {
    // Calling loadAPI creates a Retain Cycle because:
    // 1. Self (HomeVC) has a strong reference to api
    // 2. api has a strong reference to completion
    // 3. completion has a strong reference to self (HomeVC)
    func loadAPI() {
        api.completion = { (data, error) in
            self.hideLoadingIndicator()
        }
    }
}

Weak References in Swift

Weak References are one solution to retain cycles in Swift. A weak reference does not increment or decrement the reference count of an object. Since weak references do not increment the reference count of an object, a weak reference can be nil. This is because the object could be deallocated while the weak reference is pointing to it.

Retain Cycle Fixed

extension HomeVC {
    // Calling loadAPI no longer creates a Retain Cycle:
    // 1. Self (HomeVC) has a strong reference to api
    // 2. api has a strong reference to completion
    // 3. completion has a weak reference to self (HomeVC)
    func loadAPI() {
        api.completion = { [weak self] (data, error) in
            self?.hideLoadingIndicator()
        }
    }
}

Unowned References in Swift

Another solution to retain cycles is an unowned reference. Like a weak reference, an unowned reference does not increment or decrement the reference count of an object. However, unlike a weak reference, the program guarantees to the Swift compiler that an unowned reference will not be nil when it is accessed.

Accessing an unowned reference when the instance the reference points to is nil will cause a fatal program error.

Fixed Retain Cycle

extension HomeVC {
    // Calling loadAPI no longer creates a Retain Cycle:
    // 1. Self (HomeVC) has a strong reference to api
    // 2. api has a strong reference to completion
    // 3. completion has a unowned reference to self (HomeVC)
    func loadAPI() {
        api.completion = { [unowned self] (data, error) in
            self.hideLoadingIndicator()
        }
    }
}

Do UIView Animation Blocks Require Weak or Unowned Self?

Not all blocks require weak or unowned self to prevent retain cycles. UIView animation blocks run only once and are then deallocated. This means even strong references in animation blocks will be released when the block runs.

// Animation blocks do not need weak or unowned
// references to self
UIView.animate(withDuration: 0.25) {
    self.view.alpha = 0
}

Strong, Weak, and Unowned Memory Management in Swift

That’s it! By using the right combination of strong, weak, and unowned references you can prevent retain cycles and better manage memory in Swift.