TIL I finally learned when to use unowned as opposed to using weak.

Differences

  • Under the hood: unowned is essentially a force-unwrap of a weak capture, with all that it entails. Because of this using it slightly more dangerous.
  • Crash danger: Accessing an unowned reference while it’s nil will cause a crash.
  • Compilation error: Every weak reference, must be an optional property. Otherwise you’ll get a compilation error:
weak var delegate: DataEntryDelegate = DataHandler() // ERROR: 'weak' variable should have optional type 'DataEntryDelegate?'
  • Call site: Unlike weak references, it’s not mandatory for unowned references to be an optional. This makes the call site cleaner i.e. you don’t have to do the optional unwrapping dance.

Similarities

  • Both don’t keep a strong hold on the instance it refers to.

Don’t

Use unowned for async/network operations. Because the object may become nil and then accessing it would crash!

Do

Only use unowned when you’re certain that something can never be nil and you just want cleaner looking code. e.g. you need to break the reference cycle between a childVC & ParentVC. Obviously a childVC can’t be on the stack if its parent is nil. So that’s a good example.

Note: You could use weak, which then you’d have to do optional unwrapping. However it’s just not as clean as using unowned

Docs

The docs say:

In contrast, use an unowned reference when the other instance has the same lifetime or a longer lifetime

Unlike a weak reference, an unowned reference is expected to always have a value. As a result, marking a value as unowned doesn’t make it optional, and ARC never sets an unowned reference’s value to nil.

Example (from Apple):

class Customer {
    let name: String
    var card: CreditCard?
    init(name: String) {
        self.name = name
    }
    deinit { print("\(name) is being deinitialized") }
}

class CreditCard {
    let number: UInt64
    unowned let customer: Customer
    init(number: UInt64, customer: Customer) {
        self.number = number`
        self.customer = customer
    }
    deinit { print("Card #\(number) is being deinitialized") }
}

Because a credit card will always have a customer, you define its customer property as an unowned reference, to avoid a strong reference cycle.

How does the crash look like?

class ViewController: UIViewController {
    var original: UIViewController?
    unowned var unowned: UIViewController = UIViewController() // doing this just because we need to initialize all non-optional properties...
    
    override func viewDidLoad() {
        super.viewDidLoad()
        original = UIViewController()
        unowned = original!
        
        original = nil
        print(unowned) // Thread 1: signal SIGABRT --- Fatal error: Attempted to read an unowned reference but the object was already deallocated
        print(original!) // Fatal error: Unexpectedly found nil while unwrapping an Optional value
    }
}

A crash for an unowned reference that’s been deallocated is:

signal SIGABRT — Fatal error: Attempted to read an unowned reference but the object was already deallocated

Why is it named unowned?

Naming it “unowned” clearly communicates that the reference doesn’t contribute to ownership or memory management.
It’s meant to emphasize its passive role in memory management and the importance of carefully ensuring its validity within the expected lifespan relationship. It’s a clear and intentional choice that conveys both the benefit and the potential risk associated with this type of reference.

The naming strategy aligns with several other syntax that Apple has for ‘undefined’, ‘unreferenced’, ‘unsafe’, ‘UnsafePointer’

References: