Unowned vs weak. Are there any drastic benefits of using unowned references?


Greetings, traveler!

Before ARC (Automatic Reference Counting) was introduced, developers had to manage memory and object references manually. ARC saved us a lot of headaches due to memory-related issues. However, we still need to manage references while working with reference types.

When we don’t use weak or unowned keywords, we signal to ARC that the “strong reference” is necessary. This will prevent the reference count from dropping to zero.

These keywords need not be used every time. We only need to mark variables with them when a strong reference cycle can be created.

The difference between unowned and weak references is that weak is an Optional type while unowned isn’t. Since then, your application will crash if you try to access an unowned variable, which is nil at that moment.

But are there any drastic benefits of using unowned references when dealing with such risks? The short answer is NO.

In Swift 4, the weak reference implementation was updated, so now there is no significant decrease in performance if you choose this keyword instead of unowned. Weak references now point at the side tables instead of the object itself. And since side tables are small, this mechanism works very fast. So, the only benefit you will gain using the unowned keyword is eliminating the need to unwrap the optional value.

Hidden risks

Sure, you need to consider the risks. Let’s take a look at this example. We have the ViewModel and the ViewController. We can inject the ViewModel via the ViewController’s constructor using the protocol. The ViewModel has a function with completion that can be used by the ViewController. To prevent a strong reference cycle, we will use the unowned keyword.

import UIKit

protocol ViewModelProtocol {
    func action(_ completion: (() -> Void)?)
}

class ViewModel: ViewModelProtocol {
    
    func action(_ completion: (() -> Void)?) {
        let url = URL(string: "https://livsycode.com")!
        let request = URLRequest(url: url)
        
        URLSession.shared.dataTask(with: request) { _, _, _ in
            completion?()
        }.resume()
    }
    
}

class ViewController: UIViewController {
    
    var viewModel: ViewModelProtocol
    
    init(viewModel: ViewModelProtocol) {
        self.viewModel = viewModel
        super.init(nibName: nil, bundle: nil)
    }
    
    required init?(coder: NSCoder) {
        fatalError()
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        viewModel.action { [unowned self] in
            self.update()
        }
    }
    
    func update() {
        print("Updated")
    }
    
}

First of all, we broke the idea of ViewController’s independence. Since we inject the ViewModel via protocol, it can be any ViewModel conforming to the ViewModelProtocol. Marking the reference with the unowned keyword decreases our module’s flexibility.

The hidden risk is that there is no guarantee that these objects will still be in memory by the time we get the result of our operation. We will face a crash if it happens.

Conclusion

Understanding the reference system is crucial nowadays. But Swift is evolving fast. Today, it offers a variety of convenient tools with high performance on board. Our approaches should evolve with this beautiful language, too.