39
someFunction(completion: { [weak self] in
    self?.variable = self!.otherVariable
})

Is this always safe? I access the optional self in the beginning of the statement, and personally I assume that the second part of this statement will never be executed if self is nil. Is this true? If self indeed is nil, the second part will never happen? And it will never happen that self could be 'nilled' during this single line of code?

mfaani
  • 33,269
  • 19
  • 164
  • 293
Sti
  • 8,275
  • 9
  • 62
  • 124
  • This may helps you https://stackoverflow.com/questions/24468336/how-to-correctly-handle-weak-self-in-swift-blocks-with-arguments – Jack Jul 26 '17 at 12:48
  • 13
    It's an interesting question. Up-voted. It's easy enough to test how it works today, but is it guaranteed to *always* work is the question. It looks suspicious and I wouldn't use it even it I knew it worked. It's easy enough to use `if let` or `guard` to make your intentions clear. – vacawama Jul 26 '17 at 12:53
  • Even better question is: why would you like to use optionals that way? – user3581248 Jul 26 '17 at 12:59
  • 2
    I don't think anyone can answer the question `Is this always safe?`. There are so many edge cases with threading. What does `someFunction` do? Where is the completion called? Either way, don't do this. – JAL Jul 26 '17 at 13:12
  • Just for my own knowledge, could someone elaborate why won't expression on right side be evaluated first? Because in that case, self being nil would end up in `fatal error` – Muhammad Hassan Jul 26 '17 at 13:31
  • 1
    @MuhammadHassan, MartinR's answer below addresses that. – vacawama Jul 26 '17 at 13:32

5 Answers5

26

Optional Chaining from "The Swift Programming Language" gives the following example:

 let john = Person()
 // ...
 let someAddress = Address()
 // ...
 john.residence?.address = someAddress

followed by (emphasis added):

In this example, the attempt to set the address property of john.residence will fail, because john.residence is currently nil.

The assignment is part of the optional chaining, which means none of the code on the right hand side of the = operator is evaluated.

Applied to your case: In

self?.variable = self!.otherVariable

the right-hand side is not evaluated if self is nil. Therefore the answer to your question

If self indeed is nil, the second part will never happen?

is "yes". With regard to the second question

And it will never happen that self could be 'nilled' during this single line of code?

My original assumption was that once self has been determined to be != nil, a strong reference to self! is held throughout the evaluation of the statement, so that this can not happen. However (as @Hamish pointed out), this is not guaranteed. Apple engineer Joe Groff writes at Confirming order of operations in the Swift forum:

This isn't guaranteed. Releases may be optimized to happen earlier than this, to any point after the last formal use of the strong reference. Since the strong reference loaded in order to evaluate the left-hand side weakProperty?.variable is not used afterward, there is nothing keeping it alive, so it could be immediately released.
If there are any side effects in the getter for variable that cause the object referenced by weakProperty to be deallocated, nil-ing out the weak reference, then that would cause the force-unwrap on the right side to fail. You should use if let to test the weak reference, and reference the strong reference bound by the if let

Community
  • 1
  • 1
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • 2
    I think this is the correct answer. If the left side is `nil` of any assignment operation, the right side will not be evaluated. Consider something like this: `instance?.val = ([] as [Int])[0]` (assuming `val` is an `Int`). The right side of that expression will cause a crash, but will not be evaluated if `instance` is `nil`. – JAL Jul 26 '17 at 13:10
  • @JAL true but imagine if the left side wasn't `nil` and then because it's an asynchronous operation, before reading the right operand, `self` became `nil`. That could cause a crash right from the right operand? – mfaani Jul 26 '17 at 14:05
  • @Honey not if a strong reference to `self` was captured as the code comment in dfri's answer states. I think the lifetime of the expression there means the entire line (both sides of the assignment). – JAL Jul 26 '17 at 14:07
  • @Honey the left side not being nil doesn't apply in my (simplified) example. I'm counting on the left side being nil to just show an example of the assignment operator short-circuiting. – JAL Jul 26 '17 at 14:22
  • 1
    [According to Joe Groff](https://forums.swift.org/t/confirming-order-of-operations/16376/9), there is no guarantee that a strong reference is held for the duration of the RHS' evaluation. – Hamish Sep 26 '18 at 21:37
  • @Hamish: Swift 4.2 apparently allows to “rebind self” (without any quoting): `if let self = self { ... }` – but I cannot find anything related in the release notes, change log, or evolution proposals. Do you happen to know when that change was introduced? – Martin R Sep 27 '18 at 08:26
  • @MartinR Yes! Love that they made that change – it was introduced in https://github.com/apple/swift/pull/15306, and [SE-0079](https://github.com/apple/swift-evolution/blob/master/proposals/0079-upgrade-self-from-weak-to-strong.md) was subsequently revised from being deferred to being implemented (though note that the implementation doesn't follow the "restrictions" section of the proposal). Really such a change should have also been noted in the changelog, but it appears it wasn't. – Hamish Sep 27 '18 at 08:30
  • @Hamish: Thanks again – it seems that I did not search thoroughly in the proposals list. – Martin R Sep 27 '18 at 08:35
17

No, this is not safe

As pointed out by @Hamish in a comment below, Swift Compiler Engineer Joe Groff describes that there is no guarantee that a strong reference is held for the duration of the RHS' evaluation [emphasis mine]

Confirming order of operations

Rod_Brown:

Hi there,

I’m wondering about the safety of a type of access on a weak variable:

class MyClass {

    weak var weakProperty: MyWeakObject?

    func perform() {
        // Case 1
        weakProperty?.variable = weakProperty!.otherVariable

        // Case 2
        weakProperty?.performMethod(weakProperty!)
    }
}

With the two cases above, is it guaranteed by Swift that the weakProperty can be force unwrapped at these positions?

I’m curious about the guarantees Swift makes about access during optional chaining E.g. are the weakProperty! accessors guaranteed to only fire iff the optional chaining determines first that the value is already non-nil?

Additionally, is the weak object guaranteed to be retained for the duration of this evaluation, or can the weak variable potentially be able to deallocate between the optional access and the method being called?

Joe_Groff:

This isn't guaranteed. Releases may be optimized to happen earlier than this, to any point after the last formal use of the strong reference. Since the strong reference loaded in order to evaluate the left-hand side weakProperty?.variable is not used afterward, there is nothing keeping it alive, so it could be immediately released. If there are any side effects in the getter for variable that cause the object referenced by weakProperty to be deallocated, nil-ing out the weak reference, then that would cause the force-unwrap on the right side to fail. You should use if let to test the weak reference, and reference the strong reference bound by the if let:

if let property = weakProperty {
  property.variable = property.otherVariable
  property.performMethod(property)
}

This should safer and also more efficient, since the weak reference is loaded and tested once instead of four times.


Given the answer quoted answer by Joe Groff above, my previous answer is moot, but I will leave it here as a possibly interesting (albeit failed) journey into the depths of the Swift compiler.


Historical answer reaching an in-correct final argument, but through an interesting journey, nonetheless

I'll base this answer upon my comment to @appzYourLife:s deleted answer:

This is pure speculation, but considering the somewhat close connection between many of the experienced Swift core devs and C++:s Boost lib, I would assume that weak reference is locked into a strong one for the lifetime of the expression, if this assigns/mutates something in self, much like the explicitly used std::weak_ptr::lock() of the C++ counterpart.

Let's have a look at your example, where self has been captured by a weak reference and is not nil when accessing the left hand side of the assignment expression

self?.variable = self!.otherVariable
/* ^             ^^^^^-- what about this then?
   |
    \-- we'll assume this is a success */

We may look at the underlying treatment of weak (Swift) references in the Swift runtime, swift/include/swift/Runtime/HeapObject.h specifically:

/// Load a value from a weak reference.  If the current value is a
/// non-null object that has begun deallocation, returns null;
/// otherwise, retains the object before returning.
///
/// \param ref - never null
/// \return can be null
SWIFT_RUNTIME_EXPORT
HeapObject *swift_weakLoadStrong(WeakReference *ref);

The key here is the comment

If the current value is a non-null object that has begun deallocation, returns null; otherwise, retains the object before returning.

Since this is based on backend runtime code comment, it is still somewhat speculative, but I would say that the above implies that when attempting to access the value pointed to by a weak reference, there reference will indeed be retained as a strong one for lifetime of the call ("... until returning").


To try to redeem the "somewhat speculative" part from above, we may continue to dig into how Swift handles access of a value via a weak reference. From @idmean:s comment below (studying the generated SIL code for an example like the OP:s) we know that the swift_weakLoadStrong(...) function is called.

So we'll start by looking into the implementation of the swift_weakLoadStrong(...) function in swift/stdlib/public/runtime/HeapObject.cpp and see where we'll get from there:

HeapObject *swift::swift_weakLoadStrong(WeakReference *ref) {
  return ref->nativeLoadStrong();
}

We find the implementation of the nativeLoadStrong() method of WeakReference from swift/include/swift/Runtime/HeapObject.h

HeapObject *nativeLoadStrong() {
  auto bits = nativeValue.load(std::memory_order_relaxed);
  return nativeLoadStrongFromBits(bits);
}

From the same file, the implementation of nativeLoadStrongFromBits(...):

HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) {
  auto side = bits.getNativeOrNull();
  return side ? side->tryRetain() : nullptr;
}

Continuing along the call chain, tryRetain() is a method of HeapObjectSideTableEntry (which is essential for the object lifecycle state machine), and we find its implementation in swift/stdlib/public/SwiftShims/RefCount.h

HeapObject* tryRetain() {
  if (refCounts.tryIncrement())
    return object.load(std::memory_order_relaxed);
  else
    return nullptr;
}

The implementation of the tryIncrement() method of the RefCounts type (here invoked via an instance of a typedef:ed specialization of it) can be found in the same file as above:

// Increment the reference count, unless the object is deiniting.
bool tryIncrement() {
  ...
}

I believe the comment here suffices for us to use this method as an end point: if the object is not deiniting (which we've assumed above that it does not, as the lhs of assignment in the OP:s example is assumed to be successful), the (strong) reference count on the object will be increased, and a HeapObject pointer (backed by a strong reference count increment) will be passed to the assignment operator. We needn't study how the corresponding reference count decrement is eventually performed at the end of the assignment, but now know beyond speculation that the object associated with the weak reference will be retained as a strong one for the lifetime of the assignment, given that it has not been freed/deallocated at the time of the left hand side access of it (in which case the right hand side of it will never be processed, as has been explained in @MartinR:s answer).

dfrib
  • 70,367
  • 12
  • 127
  • 192
  • 1
    Great answer! I just had to check and the assembly indeed seems to call out to this function and also makes a call to `_swift_rt_swift_release` that seems to be the counterpart to this call. (Although I really find Swift assembly hard to follow.) – idmean Jul 26 '17 at 13:54
  • " for the lifetime of the expression" you mean `self?.variable = self!.otherVariable` The lifetime of this would be from the beginning of reading the left operand to the end of reading the right operand? – mfaani Jul 26 '17 at 14:01
  • @idmean still somewhat speculative thought, as I haven't followed the call chain or all conditions required prior to a final object release and deallocation. But the rabbit hole is a bit too deep for me to dwell down into at this moment... Good to get your generated SIL verification for the `swift_weakLoadStrong` call, thanks! – dfrib Jul 26 '17 at 14:12
  • 1
    @Honey The assignment operator is somewhat special in Swift, but when I refer to lifetime I mean until the assignment operator has finished its work with its two operands. Compare with a regular Swift operator which is just a function: when the function returns the evaluation of the expression at the callee site is finished (i.e., the call to the operator), which would correspond to the endpoint of the (somewhat speculative) lock on the `weak` reference. – dfrib Jul 26 '17 at 14:15
  • 1
    (copying my comment under Martin's answer here just so you see): [According to Joe Groff](https://forums.swift.org/t/confirming-order-of-operations/16376/9), there is no guarantee that a strong reference is held for the duration of the RHS' evaluation. – Hamish Sep 26 '18 at 21:38
3

The documentation clearly states that, if the left side of the assignment is determined to be nil, right side will not be evaluated. However, in the given example self is weak reference and may be released (and nullified) right after the optional check passes, but just before force-unwrap will happen, making the whole expression nil-unsafe.

2

Is this always safe

No. You are not doing the "weak-strong dance". Do it! Whenever you use weak self, you should unwrap the Optional safely, and then refer only to the result of that unwrapping — like this:

someFunction(completion: { [weak self] in
    if let sself = self { // safe unwrap
        // now refer only to `sself` here
        sself.variable = sself.otherVariable
        // ... and so on ...
    }
})
matt
  • 515,959
  • 87
  • 875
  • 1,141
  • @Sti This doesn't directly answer your question, which is a theoretical rather than a practical one. But it does provide you with guidance as to what should be done in practice. Force unwrapping even in situations where you know it will be fine is still not a good way to go about things. Rather than being the wrong answer, it provides you with the right way of doing things. – sketchyTech Apr 26 '18 at 15:14
-4

BEFORE CORRECTION:

I think others have answered the details of your question far better that I could.

But aside from learning. If you actually want your code to reliably work then its best to do as such:

someFunction(completion: { [weak self] in
    guard let _ = self else{
        print("self was nil. End of discussion")
        return
    }
    print("we now have safely 'captured' a self, no need to worry about this issue")
    self?.variable = self!.otherVariable
    self!.someOthervariable = self!.otherVariable
}

AFTER CORRECTION.

Thanks to MartinR's explanation below I learnt a great deal.

Reading from this great post on closure capturing. I slavishly thought whenever you see something in brackets [] it means it's captured and its value doesn't change. But the only thing we're doing in the brackets is that we are weak-ifying it and making it known to ourselves that it's value could become nil. Had we done something like [x = self] we would have successfully captured it but then still we'd have the problem of holding a strong pointer to self itself and be creating a memory cycle. (It's interesting in the sense it's a very thin line from going to creating a memory cycle to creating a crash due to the value being deallocated because you weak-ified it).

So to conclude:

  1. [capturedSelf = self]

    creates memory cycle. Not good!

  2. [weak self] 
    in guard let _ = self else{
    return
    } 
    

    (can lead to crash if you force self unwrap afterwards) the guard let is it utterly useless. Because the very next line, still self can become nil. Not good!

  3. [weak self] 
    self?.method1()
    

    (can lead to crash if you force self unwrap afterwards. Would go through if self is non-nil. Would fail safely if self is nil.) This is what most likely what you want. This is Good!

  4. [weak self] in 
    guard let strongSelf = self else{ 
    return
    } 
    

    Will fail safely if self was deallocated or proceed if not nil. But it kinda defeats the purpose, because you shouldn't need to communicate with self when it removed it's own reference. I can't think of a good use case for this. This is likely useless!

mfaani
  • 33,269
  • 19
  • 164
  • 293
  • I don't see how this is different (or better) than [Matt's answer](https://stackoverflow.com/a/45329206/2415822). – JAL Jul 26 '17 at 14:20
  • @JAL hah. didn't see. I guess mine is more descriptive and uses guard which is likely better, but you're right our answers are 90% the same. – mfaani Jul 26 '17 at 14:22
  • 3
    It is even worse. Not strong reference to self is taken here explicitly. – idmean Jul 26 '17 at 14:24
  • 2
    Note that `guard let _ = self` does *not* capture self safely. You have to bind it e.g. as `guard let strongSelf = self` and then use `strongSelf` in the closure. – Martin R Jul 26 '17 at 14:24
  • @MartinR isn't it captured at the point that you do `[weak self]`? So inside the closure the value says the same...and what I'm doing is seeing if the captured value is `nil` or not... – mfaani Jul 26 '17 at 14:25
  • 2
    `[weak self]` only means that self is captured weakly, and can be nil if the closure is called. You verify `self!=nil` on the entry, but it could become nil later. – Martin R Jul 26 '17 at 14:29
  • 5
    If you're going to leave the answer, don't just put "This answer is incorrect" at the top. Edit the answer to *explain* what the mistake you originally made was, *why* it's wrong, and how to avoid it. In other words, if it's the comments you think are valuable, integrate them into the answer itself, giving credit to the users who originally posted them (by name). – Cody Gray - on strike Jul 26 '17 at 15:53
  • @CodyGray Very valid point. I made an edit and tried fixing as much. Perhaps you can write a better answer to [this meta post](https://meta.stackoverflow.com/questions/256312/should-i-fix-my-incorrect-answer-so-as-to-make-it-pretty-similar-to-existing-one). – mfaani Jul 26 '17 at 17:06