Strong reference cycles

A strong reference cycle is when two instances directly or indirectly hold strong references to each other. This means that neither object can ever be deleted, because both are ensuring that the other will always exist.

This scenario is our first really bad memory management scenario. It is one thing to keep memory around longer than it is needed; it is a whole different level to create memory that can never be freed up to be reused again. This type of memory problem is called a memory leak, because the computer will slowly leak memory until there is no longer any new memory available. This is why you will sometimes see a speed improvement after restarting your device. Upon restart, all of the memory is freed up again. Modern operating systems will sometimes find ways to forcefully free up memory, especially when completely quitting an app, but we cannot rely on this as programmers.

So how can we prevent these strong reference cycles? First, let's take a look at what they look like. There are two main scenarios where these cycles can exist: between objects and with closures.

Between objects

A strong reference cycle between objects is when two types directly or indirectly contain strong references to each other.

Spotting

A great example of a strong reference cycle between objects is if we rewrite our preceding car example without using a weak reference from SteeringWheel to Car:

class SteeringWheel {
    var car: Car?
}

class Car {
    var steeringWheel: SteeringWheel
    
    init(steeringWheel: SteeringWheel) {
        self.steeringWheel = steeringWheel
        self.steeringWheel.car = self
    }
}

The only difference between this code and the preceding code is that the car property on SteeringWheel is no longer declared as weak. This means that when a car is created, it will set up a strong relationship to the SteeringWheel instance and then create a strong reference from the SteeringWheel instance back to the car:

Spotting

This scenario means that the reference count of both instances can never go down to zero and therefore they will never be deleted and the memory will be leaked.

Two objects can also indirectly hold strong references to each other through one or more third parties:

class Manufacturer {
    var cars: [Car] = []
}

class SteeringWheel {
    var manufacturer: Manufacturer?
}

class Car {
    var steeringWheel: SteeringWheel?
}

Here, we have the scenario where a Car can have a strong reference to a SteeringWheel that can have a strong reference to a Manufacturer that in turn has a strong reference to the original Car:

Spotting

This is another strong reference cycle and it illustrates two more important points. First, optionals, by default, still create strong relationships when not nil. Also, the built in container types, such as arrays and dictionaries, also create strong relationships.

Clearly strong reference cycles can be difficult to spot, especially because they are hard to detect in the first place. An individual memory leak is rarely going to be noticeable to a user of your program, but if you continuously leak memory over and over again, it can cause their device to feel sluggish or even crash.

The best way as a developer to detect them is to use a tool built into Xcode called Instruments. Instruments can do many things, but one of those things is called Leaks. To run this tool you must have an Xcode Project; you cannot run it on a Playground. It is run by selecting Product | Profile from the menu bar.

This will build your project and display a series of profiling tools:

Spotting

If you select the Leaks tool and press the record button in the upper-left corner, it will run your program and warn you of memory leaks which it can detect. A memory leak will look like a red X icon and will be listed as a leaked object:

Spotting

You can even select the Cycles & Roots view for the leaked objects and Instruments will show you a visual representation of your strong reference cycle. In the following screenshot, you can see that there is a cycle between SteeringWheel and Car:

Spotting

Clearly, Leaks is a powerful tool and you should run it periodically on your code, but it will not catch all strong reference cycles. The last line of defense is going to be you staying vigilant with your code, always thinking about the ownership graph.

Of course, spotting cycles is only part of the battle. The other part of the battle is fixing them.

Fixing

The easiest way to break a strong reference cycle is to simply remove one of the relationships completely. However, this is very often not going to be an option. A lot of the time, it is important to have a two-way relationship.

The way we fix cycles without completely removing a relationship is to make one or more of the relationships weak or unowned. In fact, this is the main reason that these other two types of relationships exist.

We fix the strong reference cycle in our original example by changing the car relationship back to weak:

class SteeringWheel {
    weak var car: Car?
}

class Car {
    var steeringWheel: SteeringWheel
    
    init(steeringWheel: SteeringWheel) {
        self.steeringWheel = steeringWheel
        self.steeringWheel.car = self
    }
}

Now Car has a strong reference to SteeringWheel but there is only a weak reference back:

Fixing

How you break any given cycle is going to depend on your implementation. The only important part is that somewhere in the cycle of references there is a weak or unowned relationship.

Unowned relationships are good for scenarios where the connection will never be missing. In our example, there are times that a SteeringWheel exists without a car reference. If we change it so that the SteeringWheel is created in the Car initializer, we could make the reference unowned:

class SteeringWheel2 {
    unowned var car: Car
    
    init(car: Car) {
        self.car = car
    }
}

class Car {
    var steeringWheel: SteeringWheel2!
    
    init() {
        self.steeringWheel = SteeringWheel2(car: self)
    }
}

Also, note that we had to define the steeringWheel property as an implicitly unwrapped optional. This is because we had to use self when initializing it but at the same time we cannot use self until all the properties have a value. Making it optional allows it to be nil while we are using self to create the steering wheel. This is safe as long as the SteeringWheel2 initializer doesn't try to access the steeringWheel property of the passed in car.

With closures

As we found out in Chapter 5, A Modern Paradigm – Closures and Functional Programming, closures are just another type of object, so they follow the same ARC rules. However, they are subtler than classes because of their ability to capture variables from their surrounding scope. These captures create strong references from the closures to the captured variable that are often overlooked because capturing variables looks so natural compared to conditionals, for loops and other similar syntax.

Just as classes can create circular references, so can closures. Something can have a strong reference to a closure that directly or indirectly has a strong reference back to the original object. Let's take a look at how we can spot that.

Spotting

It is very common to provide closure properties that will be called whenever something occurs. These are generally called callbacks. Let's look at a ball class that has a callback for when the ball bounces:

class Ball {
    var location: (x: Double, y: Double) = (0,0)
    
    var onBounce: (() -> ())?
}

This type of setup makes it easy to inadvertently create a strong reference cycle:

let ball = Ball()
ball.onBounce = {
    print("(ball.location.x), (ball.location.y)")
}

Here, we are printing out the location of the ball every time it bounces. However, if you consider this carefully, you will see that there is a strong reference cycle between the closure and the ball instance. This is because we are capturing the ball within the closure. As we have learned already, this creates a strong reference from the closure to the ball. The ball also has a strong reference to the closure through the onBounce property. That is our circle.

You should always be conscious of what variables are being captured in your closures and if that variable directly or indirectly has a strong reference to the closure itself.

Fixing

To fix these types of strong reference cycles with closures we will again need to make one part of the circle weak or unowned.

Swift does not allow us to make closure references weak, so we have to find a way to capture the ball variable weakly instead of strongly.

To capture a variable weakly, we must use a capture list. Using a capture list, we can capture a weak or unowned copy of the original variable. We do so by specifying the weak or unowned variables before the capture list variable name:

ball.onBounce = { [weak ball] in
    print("(ball?.location.x), (ball?.location.y)")
}

By declaring the ball copy as weak, it automatically makes it optional. This means that we had to use optional chaining to print out its location. Just like with other weak variables, ball will be set to nil if the ball is deleted. However, based on the nature of the code, we know that this closure will never be called if ball is deleted, since the closure is stored right on the ball instance. In that case, it is probably better to use the unowned keyword:

ball.onBounce = { [unowned ball] in
    print("(ball.location.x), (ball.location.y)")
}

It is always nice to clean up your code by removing unnecessary optionals.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset