8

In Swift, we have normal default typing

  • the object simply cannot become nil.

we have weak typing

  • the object can become nil. if the object becomes nil, your pointer automatically becomes nil, so you know that the object became nil

and we have unowned typing

  • the object can become nil. if the object becomes nil, nothing whatsoever happens to your pointer - you're screwed if you try to use it

(So: by corollary: the one and only time you can use "unowned" is if you "absolutely know" the object will never become nil.)

Now: it seems to me that the following sentence is absolutely true ... and by absolutely I mean, really, truly, absolutely, down to the deepest possible philosophical concerns true...

"The only difference between unowned and weak, is performance. Since unowned has no checking, it is faster. There is absolutely no other difference."

and hence the logical corollary:

"There is, absolutely, no reason to use unowned, other than if the extra performance over weak is needed."

(Aside - the only other difference I can think of is in the self-documenting sense. If I use unowned, it cues my fellow developers to certain things; let us set aside that issue for now.)

So my question is straightforward, very exact, very specific: are the bold sentences above "true" (in the "utterly, very, spectacularly" true sense of true).

bdesham
  • 15,430
  • 13
  • 79
  • 123
Fattie
  • 27,874
  • 70
  • 431
  • 719

4 Answers4

3

The bold sentences are not true.

Weak is optional and can be set at any time.

Unowned is non-optional but can be nil. If that happens and you call it your app crashs. It has to be set during initialization.

Another difference is performance, as the author stated. Unowned does not do any checks and is slighty faster than weak.

It better shows the relationship between classes.

You can check out this SO question for further details: What is the difference between a weak reference and an unowned reference?

Community
  • 1
  • 1
Yannick
  • 3,210
  • 1
  • 21
  • 30
  • `Unowned` is non-optional on the surface, but it may suddenly become nil (or invalid pointer, would better phrasing) and wreck your app if it's used incorrectly. – user28434'mstep Jan 24 '17 at 14:44
  • True, but I did not want to go into too much detail here. The posted link covers this in much more detail plus the author actually said it himself. – Yannick Jan 24 '17 at 14:49
  • Hey @Yannick - thanks for that; I'm inclined to say "but are there any differences other than that" :) that would maybe not be in the spirit of my question though :) – Fattie Jan 24 '17 at 14:49
  • hi @user28434 - yes, of course: that's the whole point of the question here. obviously unowned can go to hell by dangling: my question, is there *any advantage at all* other than pure performance? – Fattie Jan 24 '17 at 14:50
  • @JoeBlow, well small performance gain + non-optional "interface", like `T!` vs `T?`. – user28434'mstep Jan 24 '17 at 14:55
3

I agree with Yannick. Your bold statements are not correct. An unowned reference must be valid for its lifetime. In an -Ounchecked program, failure to maintain this precondition is undefined behavior. I don't mean "it crashes." I mean it is not a well-formed program; it is undefined what it does. A weak reference cannot generate undefined behavior due to its release, even under -Ounchecked.

Using unowned is a statement by the programmer that the reference will be valid over its entire lifetime. That's not even something Type! asserts. ! types just assert that the reference will be valid at the point that it is accessed. That's why you can't test x == nil on an unowned. It is not optional. It's not "optional in disguise" (like Type!). It must always be valid.

Unlike a weak reference, however, an unowned reference is used when the other instance has the same lifetime or a longer lifetime. ... An unowned reference is expected to always have a value. —— [The Swift Programming Language]

So to your "deepest possible philosophical," unowned includes a precondition that does not exist in weak. This precondition exists outside the program, and must be proven by programmer, not the compiler, in order to ensure a well-formed program.

To whether there is a reason to use unowned, there certainly is if we're taking an absolutest stance (as in your question). It is the tightest type in cases where the precondition is known to be true. weak is a weaker type than unowned; it expresses fewer preconditions. Good type theory encourages us to use the strongest (most restrictive; fewest legal values) types we can, and unowned is a stronger type than weak.

In a non-absolutist ("practical") sense, the result of picking a stronger type is simpler code. When you use weak, you have to constantly re-assert the precondition that it is not nil every time you use it and handle the cases where it is (possibly inserting fatalError which just reinvents unowned with more work). Using unowned lets you assert this precondition one time. This creates simpler, more correct code. I've never used unowned for speed. I've always used it to avoid answering over and over again "but what if it's nil?" in code where it must never be nil.

Pang
  • 9,564
  • 146
  • 81
  • 122
Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • 1
    It's a deep thought that unowned is *stronger* ... nice. Awesome. – Fattie Jan 24 '17 at 17:15
  • 1
    "I've always used it to avoid answering over and over again "but what if it's nil?" in code where it must never be nil." That's deeply insightful and really answers the "heart" of the question, I think. – Fattie Jan 24 '17 at 17:16
2

First, it seems you're conflating two different concepts in Swift: nilability (nullability) and ARC ownership reference types.

In Swift, variables (regardless of whether they're value or reference types) can only be set to nil (by your code or the type system) if they're wrapped in Optional<MyType> (AKA MyType?/MyType!) because nil is just an alias for Optional enum's .none case. (Caveat: This discussion is only about managed Swift/Obj-C objects; it excludes direct memory access via Unmanaged/UnsafePointer/etc.)

Swift object references tell ARC (the Automatic Reference Counting system) how to count ownership of an object, and it gives you the options of strong (the default), weak, unowned (AKA unowned(safe)), and unowned(unsafe). All of those can be used with or without Optional-wrapping except weakweak is a special case that requires Optional-wrapping.

  • A strong reference maintains a +1 reference count on the object. So as long as your variable is in scope the object is guaranteed to remain in scope.

    If your object ref is wrapped in an Optional, that won't have any affect on the reference counting— when the optional is .some(myTypeReference) it's the same as a non-optional, and when the optional is .none/nil, it's the same as your variable being out of scope (e.g. you had a local var ref in a function and the function has ended).

  • A weak reference does not affect the reference count of the object directly, and requires the variable to be wrapped in an Optional. When the number of strong references to the object elsewhere drops down to 0 refs, Swift/Obj-C/LLVM will deinit/dealloc the object, and all weak Optional<MyType> refs are set to .none/nil.

  • An unowned / unowned(safe) reference is effectively a weak reference without the Optional wrapping, and where the Swift runtime functionality doesn't do the nil-setting when the object is deallocated but will abort hard if you try to use the reference after it's been inited.

    It's best to think about it in terms of what happens in the Swift runtime source code— whenever you try to use an unowned reference, Swift effectively runs if (object->refCounts == 0) swift::swift_abortRetainUnowned(object);. (The actual implementation is a bit more complex and does stuff ahead of time; see the HeapObject source.)

    You can use an Optional wrapping with unowned but like strong, that doesn't directly affect the object ownership— if the optional is .some(myTypeReference) then everything above about unowned applies, and if the optional is .none then none of it applies (effectively the same as a non-Optional that has gone out of scope, as above).

  • An unowned(unsafe) reference is effectively a plain C pointer to your object— MyType *myVar. According to the Swift Book “If you try to access an unsafe unowned reference after the instance that it refers to is deallocated, your program will try to access the memory location where the instance used to be, which is an unsafe operation.” so the same semantics as a raw C pointer.

    There's a little more than goes on in the Swift runtime with unowned(unsafe) in debug builds, but I believe in release builds that's stripped out and it is literally an unsafe memory pointer. (That's true for a lot of things in Swift; e.g. simd operations are horribly slowly implemented with Swift code and for loops in debug builds, but in release builds they compile into simple simd opcodes).

It really is helpful to think of NULL in Swift as not existing; instead you have a built-in Optional enum that can be .none or can be .some(…) with a value type's value or a nonnull reference type's pointer. And only Swift code you write or a Swift lib you use changes the Optional's state, with a special exception for only weak.


So to address your assertions and questions:

“we have weak typing: the object can become nil”

Effectively, but not exactly. Weak requires Optional wrapping around your var's type and the Swift runtime will set that Optional to .none/nil for you.

“we have unowned typing: the object can become nil”

False. The object reference isn't required to be wrapped in Optional, so there is no such thing as a nil/.none state for that reference.

Also, unowned is not a typing (it doesn't change the type of data you're referencing), it's a variable annotation (it affects how arc handles memory management when your variable is in scope or is used).

True assertions would be:

  • “we have unowned/unowned(safe) variable annotation: the runtime will abort if the object reference is used after the object has been dealloced”
  • “we have unowned(unsafe) variable annotation: the reference will just use the literal RAM if it can (which can lead to garbage data for encapsulated value type vars, and EXC_BAD_ACCESS for encapsulated ref type vars)”

“The only difference between unowned and weak, is performance. Since unowned has no checking, it is faster. There is absolutely no other difference.”

False. Unowned(safe) and weak are similar in implementation— the difference is that when the object is deallocated, the compiler will flag an unowned(safe) var to abort the process if used, whereas a weak var is expected to be Optional-wrapped (nilable) and the runtime will set the Optional to nil for you.

Taking that as “The only difference between unowned(unsafe) and weak…”— sort of true but not exactly. Again, weak is only allowed on a variable of Optional<…> type, so there's a lot of tracking of the object so it can set your Optional to .none. unowned(unsafe) is far closer to a strong reference without the ref count +1— it just accesses the memory, so yeah, there would be a mild performance increase there.

So change that to:

  • “The only difference between unowned/unowned(safe) and weak, is the runtime will set the weak var to nil for you, whereas unowned(safe) will check if the object is still alive, and abort the process if not.”
  • “The only difference between unowned(unsafe) and strong, is the runtime maintains +1 ref count for every strong reference, guaranteeing the object is kept alive, whereas unowned(unsafe) doesn't touch the ref count, and just assumes the memory it points to is correct.”

"There is, absolutely, no reason to use unowned, other than if the extra performance over weak is needed."

For unowned(safe), there's no performance benefit, so there's no real reason to use unowned, period. It should be noted that I read somewhere that the intent of unowned is to be unowned(safe) in debug builds, and unowned(unsafe) in release builds, but I'm not sure if that's actually implemented in the current Swift runtime.

The Swift docs say “Unlike a weak reference, however, an unowned reference is used when the other instance has the same lifetime or a longer lifetime.” So effectively, there's no reason to use unowned(safe) instead of weak. But semantically you might want a stronger check that the code is only run when the object exists— the same use-case of using MyType! instead of MyType?.

So the benefit is that unowned(safe) will fail hard and fast if the object is gone. Weak requires using an Optional which has a .none state that your code can set explicitly. unowned(safe) does not— it's a valid reference as soon as it's assigned an object ref, and permanently invalid as soon as the object has begun deallocating.

Taking that as "There is, absolutely, no reason to use unowned(unsafe), other than if the extra performance over weak is needed."

There are situations where the compiler has already invalidated weak and unowned(safe) references, but you know the data's still there— calling code from within deinit { … } is a common use-case, since the Swift runtime invalidates weak/unowned(safe) before the reference-owning object's deinit starts. So for example if you have a class that needs to run a self-capturing closure one last time during deinit, that closure needs to be { [unowned(unsafe) self] in … }— using [weak self] will already be nil and using [unowned self] will already be abort-triggered (and of course, just using [self] will cause retain cycles and the deinit will never be called).

There are also situations where the memory you're accessing may not be managed by ARC (maybe you're doing something unusual in C code with remote objects or lazy-deserialized objects, or a bridge to another language like Obj-C or C++ or Ruby or C#). So yeah, any situation where ARC actually isn't handling the lifespan of the object because you're doing something special on the C/Obj-C side— which is less uncommon than you think, since Swift bridges nicely to Obj-C, and Obj-C can either be in ARC or MRR mode (Manual Retain Release). Unusual Obj-C MRR lifecycles weren't uncommon 1-2 decades ago before ARC (Modern Objective-C / LLVM 3.0 / Xcode 4.2 / 2011), when many Objective-C programmers were very familiar with handling memory themselves like in C/C++.

Slipp D. Thompson
  • 33,165
  • 3
  • 43
  • 43
  • My God ......... – Fattie Jan 04 '23 at 12:00
  • 2
    @Fattie Yep, a lot. I was looking into the differences between Swift ownership kinds and didn't find info that I was satisfied with, so in the process of reading through the Swift Book, finding other answers on SO, and searching through the source code for Swift itself, I ended up writing my findings in response to your old question. `¯\_(ツ)_/¯` – Slipp D. Thompson Jan 06 '23 at 03:37
  • 2
    The sort of detail I live for, thanks! :) bounty'd – Fattie Jan 06 '23 at 15:42
  • This answer needs more upvotes. Amazing response – joehinkle11 Aug 30 '23 at 22:46
1

From swift doc

"Swift also provides unsafe unowned references for cases where you need to disable runtime safety checks—for example, for performance reasons. As with all unsafe operations, you take on the responsibility for checking that code for safety.

You indicate an unsafe unowned reference by writing unowned(unsafe). If you try to access an unsafe unowned reference after the instance that it refers to is deallocated, your program will try to access the memory location where the instance used to be, which is an unsafe operation."

It seems that performance gain comes only when you use unowned(unsafe) to be precise and by default we use unowed(safe).

This is from one of the answer

unowned(safe) is a non-owning reference that asserts on access that the object is still alive. It's sort of like a weak optional reference that's implicitly unwrapped with x! every time it's accessed. unowned(unsafe) is like __unsafe_unretained in ARC—it's a non-owning reference, but there's no runtime check that the object is still alive on access, so dangling references will reach into garbage memory. unowned is always a synonym for unowned(safe) currently, but the intent is that it will be optimized to unowned(unsafe) in -Ofast builds when runtime checks are disabled.

Community
  • 1
  • 1
Kraming
  • 217
  • 3
  • 8