ARC and Memory Management in Swift

In this tutorial, you’ll learn how ARC works and how to code in Swift for optimal memory management. You’ll learn what reference cycles are, how to use the Xcode 10 visual debugger to discover them when they happen and how to break them using an example of a reference cycle in practice. By Mark Struzinski.

Leave a rating/review
Download materials
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

Weak References

To break strong reference cycles, you can specify the relationship between reference counted objects as weak.

Unless otherwise specified, all references are strong and impact reference counts. Weak references, however, don’t increase the reference count of an object.

In other words, weak references don’t participate in the lifecycle management of an object. Additionally, weak references are always declared as optional types. This means when the reference count goes to zero, the reference can automatically be set to nil.

WeakReference

In the image above, the dashed arrow represents a weak reference. Notice how the reference count of object1 is 1 because variable1 refers to it. The reference count of object2 is 2, because both variable2 and object1 refer to it.

While object2 references object1, it does so weakly, meaning it doesn’t affect the strong reference count of object1.

When both variable1 and variable2 go away, object1 will have a reference count of zero and deinit will run. This removes the strong reference to object2, which subsequently deinitializes.

Back in the Phone class, change the owner declaration to match the following:

weak var owner: User?

This breaks the User to Phone reference cycle by making the owner reference weak.

UserIphoneCycleWeaked

Build and run again. Now user and phone deallocate properly once the runScenario() method exits scope.

Unowned References

There is another reference modifier you can use that doesn’t increase the reference count: unowned.

What’s the difference between unowned and weak? A weak reference is always optional and automatically becomes nil when the referenced object goes away.

That’s why you must define weak properties as optional var types for your code to compile: The property needs to change.

Unowned references, by contrast, are never optional types. If you try to access an unowned property that refers to a deinitialized object, you’ll trigger a runtime error comparable to force unwrapping a nil optional type.

Table

Time to get some practice using unowned.

Add a new class CarrierSubscription at the end of MainViewController.swift:

class CarrierSubscription {
  let name: String
  let countryCode: String
  let number: String
  let user: User
              
  init(name: String, countryCode: String, number: String, user: User) {
    self.name = name
    self.countryCode = countryCode
    self.number = number
    self.user = user
    
    print("CarrierSubscription \(name) is initialized")
  }

  deinit {
    print("Deallocating CarrierSubscription named: \(name)")
  }
}        

CarrierSubscription has four properties:

  • Name: Name of the subscription.
  • CountryCode: Country of the subscription.
  • Number: Phone number.
  • User: Reference to a Userobject.

Who’s Your Carrier?

Next, add the following to User after the name property:

var subscriptions: [CarrierSubscription] = []

This adds a subscriptions property, which holds an array of CarrierSubscription objects.

Also, add the following to the top of the Phone class, after the owner property:

var carrierSubscription: CarrierSubscription?

func provision(carrierSubscription: CarrierSubscription) {
  self.carrierSubscription = carrierSubscription
}

func decommission() {
  carrierSubscription = nil
}

This adds an optional CarrierSubscription property and two new methods to provision and decommission a carrier subscription on the phone.

Next, add the following to init inside CarrierSubscription, just before the print statement:

user.subscriptions.append(self)

This adds CarrierSubscription to the user’s array of subscriptions.

Finally, add the following to the end of runScenario():

let subscription = CarrierSubscription(
  name: "TelBel", 
  countryCode: "0032",
  number: "31415926", 
  user: user)
iPhone.provision(carrierSubscription: subscription)

This creates a CarrierSubscription for user and provisions iPhone with it.

Build and run. Notice the console printout:

User John was initialized
Phone iPhone Xs was initialized
CarrierSubscription TelBel is initialized

Again, you see a reference cycle: Neither user, iPhone or subscription gets deallocated at the end.

Can you find where the issue is now?

Break the Chain

Either the reference from user to subscription or the reference from subscription to user should be unowned to break the cycle. The question is, which of the two to choose. This is where a little bit of knowledge of your domain helps.

A user owns a carrier subscription, but, contrary to what carriers may think, the carrier subscription does not own the user.

Moreover, it doesn’t make sense for a CarrierSubscription to exist without an owning User. This is why you declared it as an immutable let property in the first place.

Since a User with no CarrierSubscription can exist, but no CarrierSubscription can exist without a User, the user reference should be unowned.

Change the user declaration in CarrierSubscription to the following:

unowned let user: User

user is now unowned, breaking the reference cycle and allowing every object to deallocate. Build and run to confirm.

Reference Cycles With Closures

Reference cycles for objects occur when properties reference each other. Like objects, closures are also reference types and can cause cycles. Closures capture, or close over, the objects they operate on.

For example, if you assign a closure to a property of a class, and that closure uses instance properties of that same class, you have a reference cycle. In other words, the object holds a reference to the closure via a stored property. The closure holds a reference to the object via the captured value of self.

Closure Reference

Add the following to CarrierSubscription, just after the user property:

lazy var completePhoneNumber: () -> String = {
  self.countryCode + " " + self.number
}

This closure computes and returns a complete phone number. The property is lazy, meaning that you’ll delay its assignment until the first time you use the property.

This is necessary because it’s using self.countryCode and self.number, which aren’t available until after the initializer runs.

Add the following line at the end of runScenario():

print(subscription.completePhoneNumber())

Accessing completePhoneNumber() will force the closure to run and assign the property.

Build and run, and you’ll notice that user and iPhone deallocate, but CarrierSubscription does not, due to the strong reference cycle between the object and the closure.

Capture Lists

Swift has a simple, elegant way to break strong reference cycles in closures. You declare a capture list in which you define the relationships between the closure and the objects it captures.

To illustrate how the capture list works, consider the following code:

var x = 5
var y = 5

let someClosure = { [x] in
  print("\(x), \(y)")
}
x = 6
y = 6

someClosure()        // Prints 5, 6
print("\(x), \(y)")  // Prints 6, 6

x is in the closure capture list, so you copy x at the definition point of the closure. It’s captured by value.

y is not in the capture list, and is instead captured by reference. This means that y will be whatever it is when the closure runs, rather than what it was at the point of capture.

Capture lists come in handy for defining a weak or unowned relationship between objects used in a closure. In this case, unowned is a good fit, since the closure cannot exist if the instance of CarrierSubscription has gone away.