Quantcast
Channel: Kodeco | High quality programming tutorials: iOS, Android, Swift, Kotlin, Unity, and more
Viewing all articles
Browse latest Browse all 4370

ARC and Memory Management in Swift

$
0
0

ARC-Memory-Swift-feature

As a modern, high-level programming language, Swift handles much of the memory management of your apps and allocates or deallocates memory on your behalf. It does so using a neat technology called Automatic Reference Counting, or ARC. In this tutorial, you’ll learn all about ARC and memory management in Swift.

With an understanding of this system, you can influence when the life of a heap object ends. Swift uses ARC to be extremely predictable and efficient in resource constrained environments — like iOS.

Because ARC is “automatic”, you don’t have explicitly participate in the reference counting of objects. You do, however, need to consider relationships between objects to avoid memory leaks. This is a very important requirement often overlooked by new developers.

In this tutorial, you’ll level up your Swift and ARC skills by learning the following:

  • How ARC works.
  • What reference cycles are and how to break them.
  • An example of a reference cycle in practice, and how to detect one with the latest Xcode visualization tools.
  • How to deal with mixed value and reference types.

Getting Started

Open Xcode and click File\New\Playground…. Select the iOS Platform, name it MemoryManagement and then click Next. Save it wherever you wish and then remove the boilerplate code and save the playground.

Next, add the following code to your playground:

class User {
  var name: String
 
  init(name: String) {
    self.name = name
    print("User \(name) is initialized")
  }
 
  deinit {
    print("User \(name) is being deallocated")
  }
}
 
let user1 = User(name: "John")

This defines a class called User and creates an instance of it. The class User has one property, name, an init method (called just after memory allocation) and a deinit method (called just prior to deallocation). The print statements let you see what is happening.

You will notice that the playground shows "User John is initialized\n" in the sidebar, on the print statement within init. However, you will notice that the print statement within deinit is never called. This means that the object is never deinitialized, which means it’s never deallocated. That’s because the scope in which it was initialized is never closed — the playground itself never goes out of scope — so the object is not removed from memory.

Change the let user1 initialization by wrapping it in a do like so:

do {
  let user1 = User(name: "John")
}

This creates a scope around the initialization of the user1 object. At the end of the scope, we would expect user1 to be deallocated.

Now you see both print statements that correspond to initialization and deinitialization in the sidebar. This shows the object is being deinitialized at the end of the scope, just before it’s removed from memory.

The lifetime of a Swift object consists of five stages:

  1. Allocation (memory taken from stack or heap)
  2. Initialization (init code runs)
  3. Usage (the object is used)
  4. Deinitialization (deinit code runs)
  5. Deallocation (memory returned to stack or heap)

While there are no direct hooks into allocation and deallocation, you can use print statements in init and deinit as a proxy for monitoring those processes. Sometimes “deallocate” and “deinit” are used interchangeably, but they are actually two distinct stages in an object’s lifetime.

Reference counting is the mechanism by which an object is deallocated when it is no longer required. The question here is “when can you be sure an object won’t be needed in the future?” Reference counting manages this by keeping a usage count, also known as the reference count, inside each object instance.

This count indicates how many “things” reference the object. When the reference count of an objects hits zero no clients of the object remain, so the object deinitializes and deallocates.

SchemeOne

When you initialize the User object, it starts with a reference count of 1 since the constant user1 references that object. At the end of the do block, user1 goes out of scope, the count decrements, and the reference count decrements to zero. As a result, user1 deinitializes and subsequently deallocates.

Reference cycles

In most cases, ARC works like a charm; as a developer, you don’t usually have to worry about memory leaks, where unused objects stay alive indefinitely.

But it’s not all plain sailing! Leaks can happen!

How can leaks happen? Imagine a situation where two objects are no longer required, but each reference one another. Since each has a non-zero reference count, deallocation, of both objects, can never occur.

ReferenceCycle

This is called a strong reference cycle. It fools ARC and prevents it from cleaning up. As you can see, the reference count at the end is not zero, and thus object1 and object2 are never deallocated even though they’re no longer required.

To see this in action, add the following code after the User class but before the existing do block:

class Phone {
  let model: String
  var owner: User?
 
  init(model: String) {
    self.model = model
    print("Phone \(model) is initialized")
  }
 
  deinit {
    print("Phone \(model) is being deallocated")
  }
}

Then change the do block to look like this:

do {
  let user1 = User(name: "John")
  let iPhone = Phone(model: "iPhone 6s Plus")
}

This adds a new class called Phone, and creates an instance of this new class.

This new class is rather simple: two properties, one for the model and one for the owner, an init and a deinit method. The owner property is optional, since a Phone can exist without a User.

Next, add the following code to the User class, immediately after the name property:

private(set) var phones: [Phone] = []
func add(phone: Phone) {
  phones.append(phone)
  phone.owner = self
}

This adds a phones array property to hold all phones owned by a user. The setter is made private so that clients are forced to use add(phone:). This method ensures that owner is set properly when you add it.

Currently, as you can see in the sidebar, both Phone and User objects deallocate as expected.

But now make the do block look like this:

do {
  let user1 = User(name: "John")
  let iPhone = Phone(model: "iPhone 6s Plus")
  user1.add(phone: iPhone)
}

Here, you add iPhone to user1. This automatically sets the owner property of iPhone to user1. A strong reference cycle between the two objects prevents ARC from deallocating them. As a result, both user1 and iPhone never deallocate.

UserIphoneCycle

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. Weak references, by contrast, don’t increase the strong reference count of the object.

In other words, weak references don’t participate in the lifecycle management of the 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 be left with a reference count of zero and deinit will be called. This removes the strong reference to object2 which then subsequently deinitializes.

Back in your playground, break the UserPhone reference cycle by making the owner reference weak as shown below:

class Phone {
  weak var owner: User?
  // other code...
}

UserIphoneCycleWeaked

Now user1 and iPhone deallocate properly at the end of the do block as you can see in the results sidebar.

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 deinitializes. That’s why you must define weak properties as optional var types for your code to compile (because the variable 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 will 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 before the do block as shown:

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("CarrierSubscription \(name) is being deallocated")
  }
}

CarrierSubscription has four properties: the name of the subscription, the countryCode and phone number, and a reference to a User object.

Next, add the following to the User class, after the nameproperty:

var subscriptions: [CarrierSubscription] = []

This adds a subscriptions property, which will hold an array of CarrierSubscrition 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() {
  self.carrierSubscription = nil
}

This adds an optional CarrierSubscription property and two new functions 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 ensures that this CarrierSubscription is added to the user’s array of subscriptions.

Finally, change the do block to look like this:

do {
  let user1 = User(name: "John")
  let iPhone = Phone(model: "iPhone 6s Plus")
  user1.add(phone: iPhone)
  let subscription1 = CarrierSubscription(name: "TelBel", countryCode: "0032", number: "31415926", user: user1)
  iPhone.provision(carrierSubscription: subscription1)
}

Notice what is printed in the results sidebar. Again you see a reference cycle: neither user1, iPhone or subscription1 are deallocated at the end. Can you find where the issue is now?

UserIphoneSubCycle

Either the reference from user1 to subscription1 or the reference from subscription1 to user1 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, and (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.

Add unowned to the user property of CarrierSubscription like so:

class CarrierSubscription {
  let name: String
  let countryCode: String
  let number: String
  unowned let user: User
  // Other code...
}

This breaks the reference cycle and lets every object deallocate.

UserIphoneCycleSubSolve

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 that they operate on.

For example, if a closure is assigned to the 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 declared with lazy, meaning that it will not be assigned until it’s used the first time. This is required 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 the do block:

print(subscription1.completePhoneNumber())

You will notice that user1 and iPhone deallocate, but not CarrierSubscription due to the strong reference cycle between the object and the closure.

Closure cycle

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

The variable x is in the capture list, so a copy of x is made at the point the closure is defined. It is said to be captured by value. On the other hand, y is not in the capture list, and is instead captured by reference. This means that when the closure runs, y will be whatever it is at that point, rather than 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 could not exist while the instance of CarrierSubscription has gone away.

Change the completePhoneNumber closure in CarrierSubscription to look like this:

lazy var completePhoneNumber: () -> String = {
  [unowned self] in
  return self.countryCode + " " + self.number
}

This adds [unowned self] to the capture list for the closure. It means that self is captured as an unowned reference instead of a strong reference.

This solves the reference cycle. Hooray!

The syntax used here is actually a shorthand for a longer capture syntax, which introduces a new identifier. Consider the longer form:

var closure = {
  [unowned newID = self] in
  // Use unowned newID here...
}

Here, newID is an unowned copy of self. Outside the closure’s scope, self keeps its original meaning. In the short form, which you used above, a new self variable is created which shadows the existing self variable just for the closure’s scope.

In your code, the relationship between self and the completePhoneNumber closure is unowned. If you are sure that a referenced object from a closure will never deallocate, you can use unowned. If it does deallocate, you are in trouble.

Add the following code to the end of the playground:

// A class that generates WWDC Hello greetings.  See http://wwdcwall.com
class WWDCGreeting {
  let who: String
 
  init(who: String) {
    self.who = who
  }
 
  lazy var greetingMaker: () -> String = {
    [unowned self] in
    return "Hello \(self.who)."
  }
}
 
let greetingMaker: () -> String
 
do {
  let mermaid = WWDCGreeting(who: "caffinated mermaid")
  greetingMaker = mermaid.greetingMaker
}
 
greetingMaker() // TRAP!

The playground hits a runtime exception because the closure expects self.who to still be valid, but it was deallocated when mermaid went out of scope. This example may seem contrived, but it can easily happen in real life such as when you use closures to run something much later, such as after an asynchronous network call has finished.

Change the greetingMaker variable in WWDCGreeting to look like this:

lazy var greetingMaker: () -> String = {
  [weak self] in
  return "Hello \(self?.who)."
}

There are two changes you made to the original greetingMaker closure. First, you replaced unowned with weak. Second, since self became weak, you needed to access the who property with self?.who.

The playground no longer crashes, but you get a curious result in the sidebar: "Hello, nil.". Perhaps this is acceptable, but more often you want to do something completely different if the object is gone. Swift’s guard let makes this easy.

Re-write the closure one last time, to look like this:

lazy var greetingMaker: () -> String = {
  [weak self] in
  guard let strongSelf = self else {
    return "No greeting available."
  }
  return "Hello \(strongSelf.who)."
}

The guard statement binds a new variable strongSelf from weak self. If self is nil, the closure returns "No greeting available.". On the other hand, if self is not nil, strongSelf makes a strong reference, so the object is guaranteed to live until the end of the closure.

This idiom, sometimes referred to as the strong-weak dance, is part of the Ray Wenderlich Swift Style Guide since it’s a robust pattern for handling this behavior in closures.

testskillz

Finding Reference Cycles in Xcode 8

Now that you understand the principles of ARC, what reference cycles are and how to break them, it’s time to look at a real world example.

Download the starter project and open it in Xcode 8. It needs to be Xcode 8 (or newer) because Xcode 8 added some fun new features that you’re going to use.

Build and run the project, and you’ll see the following:

ss5

This is a simple contacts app. Feel free to tap on a contact to get more information or add contacts using the + button on the top right.

Have a look at the code:

  • ContactsTableViewController: shows all the Contact objects from the database.
  • DetailViewController: shows the details for a certain Contact object.
  • NewContactViewController<: allows the user to add a new contact.
  • ContactTableViewCell: a custom table view cell to show the details of a Contact object.
  • Contact: the model for a contact in the database.
  • Number: the model for a phone number.

There is, however, something horribly wrong with the project: buried in there is a reference cycle. Your user won’t notice the issue for quite some time since the leaking objects are small — and their size makes it even harder to trace. Luckily, Xcode 8 has a new built-in tool to help you find even the smallest leaks.

Build and run the app again. Delete three or four contacts by swiping their cells to the left and tapping delete. They appear the have disappeared completely, right?

ss1

While the app is still running, move over to the bottom of Xcode and click the Debug Memory Graph button:

ss2

Observe the new kind of issues (warnings, errors, etc) in Xcode 8: Runtime Issues. They look like a purple square with a white exclamation mark inside, such as the one shown selected in this screenshot:

ss3

In the Navigator, select one of the problematic Contact objects. The cycle is clearly visible: Contact and Number objects keep each other alive by referencing one another.

4

These type of graphs are a sign for you to look into your code. Consider that a Contact can exist without a Number, but a Number should not exist without a Contact. How would you solve the cycle? Should the reference from Contact to Number or the one from Number to Contact be weak or unowned?

Give it your best shot first, then take a look below if you need help!

Solution Inside SelectShow>

Bonus: Cycles with Value Types and Reference Types

Swift types can be categorized as reference types, like classes, and value types, like structures or enumerations. The major difference is that value types are copied when they are passed around, whereas reference types share a single copy of referenced information.

Does this mean that you can't have cycles with value types? Yes: everything is copied with value types, and thus no cyclical relationships can exist since no real references are created. You need at least two references to make a cycle.

Back in your playground, add the following at the end:

struct Node { // Error
  var payload = 0
  var next: Node? = nil
}

Hmm, the compiler's not happy. A struct (value type) cannot be recursive or use an instance of itself. Otherwise, a struct of this type would have an infinite size. Change it to a class like so:

class Node {
  var payload = 0
  var next: Node? = nil
}

Self reference is not an issue for classes (i.e. reference types), so the compiler error disappears.

Now, add this to your playground:

class Person {
  var name: String
  var friends: [Person] = []
  init(name: String) {
    self.name = name
    print("New person instance: \(name)")
  }
 
  deinit {
    print("Person instance \(name) is being deallocated")
  }
}
 
do {
  let ernie = Person(name: "Ernie")
  let bert = Person(name: "Bert")
 
  ernie.friends.append(bert) // Not deallocated
  bert.friends.append(ernie) // Not deallocated
}

Here’s an example of a mixture of value types and reference types that form a reference cycle.

ernie and bert stay alive by keeping a reference to each other in their friends array, although the array itself is a value type. Make the array unowned; Xcode will show an error: unowned only applies to class types.

To break the cycle here, you’ll have to create a generic wrapper object and use it to add instances to the array. If you don’t know what generics are or how to use them, check out the Introduction to Generics tutorial on this site.

Add the following above the definition of the Person class:

class Unowned<T: AnyObject> {
  unowned var value: T
  init (_ value: T) {
    self.value = value
  }
}

Then, change the definition of the friends property in Person like so:

var friends: [Unowned<Person>] = []

And finally, change the do block to look like this:

do {
  let ernie = Person(name: "Ernie")
  let bert = Person(name: "Bert")
 
  ernie.friends.append(Unowned(bert))
  bert.friends.append(Unowned(ernie))
}

ernie and bert now deallocate happily!

The friends array isn’t a collection of Person objects anymore, but instead a collection of Unowned objects that serve as a wrapper for the Person instances.

To access the Person object within Unowned use the value property, like so:

let firstFriend = bert.friends.first?.value // get ernie

Where to Go From Here?

Here’s the download of the complete playground and the Xcode project from this tutorial.

You now have a good understanding of memory management in Swift and know how ARC works. If you want to learn more about the new debug tools in Xcode 8, I suggest watching this WWDC session.

If you want an even more in-depth look at how weak references are implemented in Swift, check out Mike Ash’s blog post Swift Weak References. It covers how weak references in Swift differ from the Objective-C implementation, and how Swift actually keeps two counts under the hood: one for strong references and one for weak references.

Finally, if you are a RW subscriber, check out the iOS 10 Screencast: Memory Graph Debugger. It gives some great tips for getting the most out of the memory visualizer.

What do you think about the ARC approach? Let me know in the comments!

The post ARC and Memory Management in Swift appeared first on Ray Wenderlich.


Viewing all articles
Browse latest Browse all 4370

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>