Understand what causes a strong reference cycle. Learn how to prevent them by defining weak and unowned properties and by defining capture lists in closures.
- [Instructor] Because Swift classes are reference types, a single class instance could have multiple references to it, so a situation can occur where two class instances hold a reference to each other, and neither can ever be de allocated, this is called a strong reference cycle, and here's an example. The teacher class has a students property that has an array of student instances. And the student class has a teacher constant which holds a teachers instance. Both classes implement deinit, to print a message when they're about to be de allocated. I'll create optional instances of teach mrsDonovan and student charlieBrown.
I'll assign mrsDonovan to charlieBrown's teacher property and add charlieBrown to mrsDonovan's student's array. I've created a strong reference cycle, to prove it, I'll set both mrsDonovan and charlieBrown to nil. And notice that neither deinit messages printed. This is because both instances hold a strong reference to each other, if, for example I create another optional instance of teacher, and then set it to nil, the deinit message is printed, because there are no other strong references holding on to that instance.
Strong references can be avoided in Swift, by marking potentially offending properties as either weak or unowned. Use a weak reference when it's possible that a property might be nil at some point in the lift of the class instance if you're defining the property as an optional, but not an implicitly unwrapped optional you'll most likely want to mark it weak. Otherwise, used unowned when a property will always have a value, for as long as the class instance is around, in this case, if I mark the teacher property of student unowned, because I'm presuming a student's teacher property will never be nil for the life of the student instance, then both instances are properly de allocated, let me show you how to use a weak property in this next example.
Both band and drummer also implement deinitializers to print a messages when instances of them are about the be deallocated, a drummer can have a band, or maybe they are a freelance studio drummer. A drummer instance's band property is an optional variable. Next, I'll create optional instances of drummer and band, once again assigning each to each other's respective properties.
And then I'll set them to nil. I don't get the deinit messages, because they're each holding a strong reference to each other, but when I mark drummer's band property as weak, the strong references are broken. Closures can also cause strong reference cycles. For example these alpha and bravo classes implement deinitializers to print a message, just like in the previous examples. Bravo has an alpha property to store an instance of alpha, and it has a couple lazy closure properties.
By adding the lazy keyword, these properties will not have their values set until the first time they are accessed. This can be helpful if, for example, the calculation to set the property value is some complex operation. The closure property printout values prints out both itself and its alpha properties, and printout alpha value only prints out its alpha property. Both of these closure properties access self. A closure captures any values from its surrounding scope for the life of the closure. So these closures will capture self, which will cause a strong reference cycle.
To demonstrate this, I'll create an optional variable instance of bravo. And then I'll set it to nil, without first accessing either of its printout values or printout alpha value closures, which would cause their values to be set. Both bravo and its alpha property are properly deallocated, however, if I just access printout values before setting bravo to nil, let alone execute the closure by writing parantheses after it, neither bravo nor the alpha instance it holds onto are deallocated when setting bravo to nil.
To prevent strong reference cycles in closures, you can define a capture list, which is a comma separated list enclosed in square brackets on the first line of a closure. I'll create a capture list for the printout value's closure. If a closure also has a parameter list or a turn type, those will be written after the capture list, before the in keyword. This is not necessary here though, so I'll undo that change. A capture list provides the means to reference the class instance itself, as weak or unowned, along with optionally defining local variables that can also be marked weak or unowned.
So, to prevent the strong reference cycle here, I will defined an unowned reference to self. And now, both our alpha and bravo instances are properly deallocated. This is because the references to self in the body of printout values are now referencing the unowned self defined in the capture list. But now if I access bravo's printout alpha value closure before setting bravo to nil, now the printout alpha value closure has created a strong reference to self, by calling self.alpha in the print statement.
It's true that I could just define an unowned self reference in the capture list, just like I did in printout values. But to demonstrate creating weak references to other values in a capture list, I'll create a weak reference to alpha here instead, the instances are still not getting deallocated though, this is because even though I defined the weak reference to alpha, I'm not using it yet, I'm still calling self.alpha in the print statement. As soon as I change it to use the weak alpha defined in the capture list, the instances get deallocated.
Learn how to write code, understand Swift's key concepts and best practices, and strengthen your programming problem-solving skills. Instructor Scott Gardner teaches the fundamentals, so you'll be prepared to develop applications for iOS, macOS, and other platforms. Completing this course will enable you to not only write first-class code, but to think like a Swift developer.
- Creating playgrounds
- Defining variables and constants
- Working with characters and strings
- Working with collections and groups
- Using operators and defining custom operators
- Controlling program flow
- Defining functions and closures
- Working with classes, structures, and enumerations
- Adopting protocols