TIL I finally learned when to use unowned as opposed to using weak.
Differences
- Under the hood:
unownedis essentially a force-unwrap of aweakcapture, with all that it entails. Because of this using it slightly more dangerous. - Crash danger: Accessing an unowned reference while it’s
nilwill cause a crash. - Compilation error: Every
weakreference, 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
unownedreference when the other instance has the same lifetime or a longer lifetime
Unlike a
weakreference, anunownedreference is expected to always have a value. As a result, marking a value asunowneddoesn’t make it optional, and ARC never sets anunownedreference’s value tonil.
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
unownedreference, 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
unownedreference 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’