In Swift, understanding memory management is essential for your app's performance and to avoid potential pitfalls, such as memory leaks. References are strong by default, but, to avoid retain cycles, you sometimes need to make use of weak references using the weak keyword.

Weak references come with a few caveats or pitfalls, such as unexpected deallocations. An instance is immediately deallocated if no other object holds a reference to it. I'm sure you have seen this warning at some point "instance will be immediately deallocated because property delegate is weak".

Let's walk you through the problem by (1) giving you an example, (2) pinpointing the problem, and (3) looking at a few solutions.

An Example

Let's start with an example. Fire up Xcode and create a playground if you'd like to follow along with me. Add an import statement for the UIKit framework.

import UIKit

We start by declaring a protocol with name ViewControllerDelegate. Note that we use AnyObject to restrict the adoption of the protocol to classes. Structs and enums cannot conform to the protocol. That is important in a moment. The ViewControllerDelegate protocol implements one method, viewControllerDidPerformAction(_:).

import UIKit

protocol ViewControllerDelegate: AnyObject {

    // MARK: - Methods

    func viewControllerDidPerformAction(_ viewController: UIViewController)

}

The next step is declaring a final class, MyDelegate, that conforms to the ViewControllerDelegate protocol. The MyDelegate class is required to implement the viewControllerDidPerformAction(_:) method.

import UIKit

protocol ViewControllerDelegate: AnyObject {

    // MARK: - Methods

    func viewControllerDidPerformAction(_ viewController: UIViewController)

}

final class MyDelegate: ViewControllerDelegate {

    // MARK: - Methods

    func viewControllerDidPerformAction(_ viewController: UIViewController) {
        print("View Controller Did Perform Action")
    }

}

Let's now implement the ViewController class. It's a UIViewController subclass as you may have guessed. It declares a property, delegate, of type ViewControllerDelegate. Note that we use the weak keyword to weakly reference the delegate. It simply means the view controller doesn't hold on to its delegate. That is very common when adopting the delegate pattern. The delegating object should not strongly reference its delegate to avoid retrain cycles. Because the delegate property is weak, it has an optional type.

The ViewController class declares one method, performAction(), in which it invokes the viewControllerDidPerformAction(_:) method on its delegate. The view controller uses optional chaining because the delegate property has an optional type.

import UIKit

protocol ViewControllerDelegate: AnyObject {

    // MARK: - Methods

    func viewControllerDidPerformAction(_ viewController: UIViewController)

}

final class MyDelegate: ViewControllerDelegate {

    // MARK: - Methods

    func viewControllerDidPerformAction(_ viewController: UIViewController) {
        print("View Controller Did Perform Action")
    }

}

final class ViewController: UIViewController {

    // MARK: - Properties

    weak var delegate: ViewControllerDelegate?

    // MARK: - Methods

    func performAction() {
        delegate?.viewControllerDidPerformAction(self)
    }

}

Let's put the pieces of the puzzle together. We create a ViewController instance and create a MyDelegate instance. We store a reference to the MyDelegate instance in the view controller's delegate property. Last, we invoke the view controller's performAction() method.

let viewController = ViewController()
viewController.delegate = MyDelegate()
viewController.performAction()

Understanding the Warning

The compiler kindly warns us that the MyDelegate instance will be immediately deallocated because the delegate property of the view controller is declared weak.

Instance Will Be Immediately Deallocated Because Property Delegate Is Weak

As a result, the delegate method isn't invoked when we executed the contents of the playground. The print statement of the viewControllerDidPerformAction(_:) method isn't executed. That isn't surprising if we consider memory management.

An instance of a class is alive for as long as at least one object holds a reference to it, and that reference needs to be strong. The problem with this example is that the view controller holds a weak reference to the delegate. Because no other object holds a strong reference to the MyDelegate instance, the MyDelegate instance is immediately deallocated. This is textbook memory management.

Because the view controller uses the weak keyword, it communicates to the compiler that it doesn't want to have a strong ownership relation with the delegate. As I wrote earlier, this is useful in situations where we want to avoid retain cycles, that is, two objects holding strong references to each other, resulting in a memory leaks.

Resolving the Warning

We have a few options to resolve the warning.

Bad Option 1: Strongly Referencing the Delegate

We can remove the weak keyword of the delegate property declaration. This immediately resolves the warning and fixes the problem.

final class ViewController: UIViewController {

    // MARK: - Properties

    var delegate: ViewControllerDelegate?

    // MARK: - Methods

    func performAction() {
        delegate?.viewControllerDidPerformAction(self)
    }

}

Why is this a bad option? When using the delegation pattern, the delegating object typically (read: almost always) holds a weak reference to its delegate to avoid a retain cycle. Take the UITableViewDelegate protocol as an example. In a typical setup, the view controller owns the UITableView instance and acts as the delegate of the table view. For that reason, a UITableView instance holds a weak reference to its delegate. Why? If the view controller strongly references its table view and the table view strongly references its delegate, the view controller, we end up with a retain cycle.

For that reason, declaring the delegate property of the ViewController class as strong isn't a wise choice.

Bad Option 2: Using a Shared Singleton

We could use a shared singleton to ensure the MyDelegate instance is never deallocated. For this example, that option is even worse than the previous option. I'm not a fan of shared singletons. Are Singletons Bad and What Is a Singleton and How To Create One In Swift explain this in more detail.

Option 3: Revising Your Architecture

The reason you are seeing this warning is most likely because your architecture is flawed in a subtle way. Let's revisit the UITableViewDelegate protocol. The view controller owns the table view and the table view holds a weak reference to its delegate, the view controller. This setup works and doesn't introduce a retain cycle.

If the delegate object is immediately deallocated because no other object holds a strong reference to it, then investigate which object should be the owner of the delegate. I cannot answer that question for you. Take a look at References, Delegation, and Notifications for more details on the delegation pattern or email me if you feel stuck.