"WEAK, STRONG, UNOWNED, for goodness sake!" - References in SWIFT

"WEAK, STRONG, UNOWNED, for goodness sake!" - References in SWIFT

I find myself constantly worrying about retain cycles when writing code. I think this is just as common as any other problem. I don't know about you, but I hear things like "When do I use the weak keyword? What the heck is this 'unowned' thing?" all the time. The problem we find ourselves in is that we know we need to use the strong, weak, and unowned specifiers to avoid retain cycles in our Swift code, but we don't quite know which one to use. Luckily, I know what they are and when to use them! Hopefully this post will teach you when and where to use these 3 specifiers.

Let’s get started

ARC

ARC is a compile time feature of Apple's version of automatic memory management. The full name is Automatic Reference Counting. It means that for an object, the memory occupied by the object will be reclaimed only when there is no strong reference pointing to it.

STRONG - strong reference

Let's start with what a strong reference is. It is essentially a normal reference (a pointer or something with the same meaning), but it is special in that it can protect the object from being reclaimed by ARC by increasing the retain count of the object pointed to by the reference by 1. In fact, as long as there is a strong reference to the object from anything, the object will not be reclaimed. Remember this, it will be used later when talking about strong reference cycles and related things.
Strong references are everywhere in Swift. In fact, when you declare a property, it is a strong reference by default. Usually, strong references are fine when the relationship hierarchy is linear. When strong references flow from parent to child, it is always fine to use strong references.
Here is an example of a strong reference.

  1. class Kraken {
  2. let tentacle=Tentacle() //Strong reference to the sublevel.  
  3. }
  4. class Tentacle {
  5. let sucker=Sucker() //Strong reference to the child level.  
  6. }
  7. class Sucker{}
  8.  
  9. */Kraken means kraken, Tentacle means tentacle, sucker means suction cup...Translator's note/*

The example is a linear relationship hierarchy. Kraken has a strong reference to the Tentacle instance, which in turn has a strong reference to the Sucker instance. The reference relationship flows from the parent level (Kraken) down to the child level (Sucker).
The reference hierarchy in the animation block is similar:

  1. UIView.animateWithDuration( 0.3 ) {
  2. self.view.alpha = 0.0  
  3. }

Because animateWithDuration is a static method of UIView, the closure here is the parent level and self is the child level.
What if the child wants to reference the parent? This is where we use weak references and unowned references.

WEAK AND UNOWNED REFERENCES

WEAK - Weak Reference

A weak reference is a pointer that cannot protect the object it refers to from being reclaimed by ARC. A strong reference can increase the retain count of its object by 1, but a weak reference cannot.
In Swift, all weak references are non-constant Optionals (think of the relationship between var and let), because when there are no other strong references to them, the reference can and will be changed to nil.
For example, the following code will not compile:

  1. class Kraken {
  2. weak let tentacle = Tentacle() //let is a constant. All weak variables must be mutable.  
  3. }

Because tentacle is a let constant. Let cannot be changed at runtime due to specification restrictions. Because weak reference variables (weak variables) will be changed to nil when there is no strong reference pointing to them, the Swift compiler requires you to declare weak reference variables as var.
The key to using weak reference variables is in places where there is a potential for strong reference cycles. A strong reference cycle occurs when two objects hold strong references to each other, and ARC will not issue the correct release message code to either instance because the two instances are protecting each other. Here is a neat picture from Apple that shows this very clearly:

Here is a great example that demonstrates a strong reference cycle, using the NSNotification API (which is still a relatively new API). Take a look at the following code:

  1. class Kraken {
  2. var notificationObserver: ((NSNotification) -> Void)?
  3. init() {notificationObserver = NSNotificationCenter.defaultCenter().addObserverForName( "humanEnteredKrakensLair" , object: nil, queue: NSOperationQueue.mainQueue()) { notification in
  4. self.eatHuman()
  5. }
  6. }
  7. deinit {
  8. if notificationObserver != nil {
  9. NSNotificationCenter.defaultCenter.removeObserver(notificationObserver)
  10. }
  11. }
  12. }

At this point we have a strong reference cycle. You see, closures in Swift are very similar to blocks in Objective-C. If a variable is declared outside a closure, referencing it inside the closure will create another strong reference. The only exception to this is when using value types, such as Ints, Strings, Arrays, and Dictionaries in Swift.
Here NSNotificationCenter retains a closure, and this closure captures self as a strong reference when you call the eatHuman() method. The problem is: we don't clear this closure until deinit, but deinit will never be called by ARC because this closure has a strong reference to the Kraken instance!
This also happens when using NSTimers and NSThread.
The solution is to use a weak reference to self in the closure's capture list. This breaks the strong reference cycle. At this point, our object reference graph becomes like this:

Making self weak will not increase the retain count of self, which allows ARC to properly destroy it at the right time.
To use weak and unowned variables in a closure, you need to use the [] syntax in the closure body. For example:

  1. let closure = { [weak self] in
  2. self?.doSomething() //Remember, all weak variables are optional types.  
  3. }

Why is weak self in square brackets? This looks weird! In Swift, we think of arrays when we see square brackets. Guess what? You can specify multiple captured values ​​in a closure! For example:

  1. let closure = { [weak self, unowned krakenInstance] in //Look at this array that captures multiple values  
  2. self?.doSomething() //weak variables are optional types  
  3. krakenInstance.eatMoreHumans() //unowned variables are not optional types  
  4. }

Looks like an array, right? Now you know why the captured values ​​are written in square brackets. Well, with what we have learned so far, adding [weak self] to the closure capture list in the notification code above can solve the strong reference cycle problem:

  1. NSNotificationCenter.defaultCenter().addObserverForName( "humanEnteredKrakensLair" , object: nil, queue: NSOperationQueue.mainQueue()) { [weak self] notification in // Using the capture list eliminates the strong reference cycle!  
  2. self?.eatHuman() //self is now an optional type!  
  3. }

#p#

Another place where weak and unowned variables are used is when using protocols to implement delegation between multiple classes, because classes in Swift are reference types. Structs and enums can also follow protocols, but they are value types. If a parent class brings a child class to use delegation, like this:

  1. class Kraken: LossOfLimbDelegate {
  2. let tentacle = Tentacle()
  3. init() {
  4. tentacle.delegate = self
  5. }
  6. func limbHasBeenLost() {
  7. startCrying()
  8. }
  9. }
  10. protocol LossOfLimbDelegate {
  11. func limbHasBeenLost()
  12. }
  13. class Tentacle {
  14. var delegate: LossOfLimbDelegate?
  15. func cutOffTentacle() {
  16. delegate?.limbHasBeenLost()
  17. }
  18. }

Then we need to use weak variables. In this case, Tentacle holds a strong reference to Kraken in the form of its own delegate property, and Kraken also has a strong reference to Tentacle in its tentacle property. We solve this by adding a weak specifier before the delegate declaration:

  1. weak var delegate: LossOfLimbDelegate?

What did you say? It doesn't compile? Well, because non-class protocols cannot be marked as weak.
At this point, we need to use a class protocol to make the proxy attribute marked as weak. Let our protocol inherit from :class.

  1. protocol LossOfLimbDelegate: class { //Protocol now inherits class  
  2. func limbHasBeenLost()
  3. }

When not to use :class? Apple's documentation says:

Use a class-only protocol when the behavior defined by a protocol requirement ensures or requires that the type that conforms to the protocol is a reference type rather than a value type.

Basically, if the reference hierarchy of your own code is the same as what I wrote above, you just add :class. For the case of using structures or enumerations, :class is not needed, because structures and enumerations are value types, and classes are reference types.

UNOWNED

Weak references and unowned references are essentially the same thing. Unowned references do not increase the retain count of the object they reference. However, the additional advantage of unowned references in Swift is that they are non-optional. This makes it more convenient to use without having to introduce optional binding. This is no different from Implicity Unwrapped Optionals.
Now it gets a bit confusing. Neither weak references nor unowned references increase the retain count. They are both used to resolve strong reference cycles. So when do we use each? Apple's documentation says:

When a reference can still become nil during its lifetime, define it as a weak reference. Conversely, if you know in advance that a reference will not become nil after it is set, define it as an unowned reference.

You know the answer: just like with implicit optional types, if you can ensure that the reference is not nil when it is used, use unowned. If you are not sure, use a weak reference.
The following is a typical example, where the self captured in a class closure does not become nil, which generates a strong reference cycle:

  1. class RetainCycle {
  2. var closure: (() - > Void)!
  3. var string = "Hello"  
  4. init() {
  5. closure = {
  6. self.string = "Hello, World!"  
  7. }
  8. }
  9. }
  10. //Initialize the class and activate the strong reference cycle.
  11. let retainCycleInstance = RetainCycle ()
  12. retainCycleInstance.closure() //At this point we can ensure that the self captured in the closure will no longer be nil. Any subsequent code (especially code that changes the reference to self) needs to determine whether unowned still works here.

In the example above, the closure captures self as a strong reference, and self also retains a strong reference to the closure through its own closure property, which creates a strong reference cycle. Simply adding [unowned self] to the closure can break this cycle:

  1. closure = { [unowned self] in
  2. self.string = "Hello, World!"  
  3. }

Because we call the closure immediately after initializing the RetainCycle class, we can assume that self will no longer be nil.

in conclusion

Strong reference cycles are bad. But if you write your code carefully, think about your reference hierarchy, and use weak and unowned references appropriately, you can avoid memory leaks and memory abandonment. I hope this article will help you.
Happy programming, coders!

<<:  From the perspective of player needs, how can RPG mobile games design excellent social systems?

>>:  Do you really understand real-time computing?

Recommend

How much does it cost to develop a chemical industry mini program in Linxia?

How much does it cost to be an agent of Linxia Ch...

"Fake" is also very powerful, aerospace simulation products are very useful

Recently, the Indian Space Research Organization ...

How to plan the core selling point of a product: 5 universal formulas

How to plan the selling points of a product and m...

Cainiao Station is being held hostage

"I started to transfer the Cainiao Express S...

This fleshy worm knows the "gun fighting technique" in the "God Drama"

Recently, the sand dollar has become a hot topic ...

How are the App Store rankings determined? How to get on the top of the rankings

In the past two years, Xu Huaizhe and Liu Xiong h...

When persimmon meets the intestinal tract

Many people have had this experience: after eatin...

Sometimes, involution is also a waste of time | Scientific reverie

When paddling hard, don't forget to look up a...

10 ways to increase APP download and registration rate!

On my first day at work in 2019, I was assigned n...

Automatic platform for re-trading old projects, with unlimited daily income

Old project replay automatic hanging platform, si...

Write over 800MB/s! SATA Express

SATA Express is a new interface standard that intr...