Confirming order of operations

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?

I think there's always going to be a strong_retain (or similar) emitted to keep the object around when optional chaining succeeds.

In your example, those retains show up before calling into the arbitrary code of the subexpressions that follow your optional chaining, and aren't released until those calls finish.

This SIL diagram might help:

2 Likes

Thanks, that’s great.

Would there any guarantees in the language definition that this will remain the same into the future?

Regardless of what Swift guarantees, both of these techniques seem suspect from a readability standpoint; someone reading your code would be right to be very concerned about those force unwraps.

Most folks deal with this by taking a strong reference at the top of the function. That works pretty well in a toy example like the one you posted:

func perform() {
    guard let strong = weakProperty else { return }
    strong.variable = strong.otherVariable
    strong.performMethod(strong)
}

Is there something about your real program that makes the strong reference technique inconvenient.

Share and Enjoy

Quinn “The Eskimo!” @ DTS @ Apple

2 Likes

Thanks Quinn,

I’d agree, personally. I do this all the time for the same reasons. This was a question following a discussion within our team with some members who prefer this style, and a following discussion the safety of the approach found in existing parts of our codebase.

My apologies for any inconvenience, but I’m really glad for the responses. Has been a great learning experience.

A slightly different approach that will guarantee the safety can look like this:

func perform() {
  weakProperty.map {
    $0.variable = $0.otherVariable
    $0.performMethod($0)
  } // this map will return `Void`
}
1 Like

Hmm. What style, exactly?

If it were this:

        weakProperty!.variable = weakProperty!.otherVariable

then I can see a stylistic argument (avoiding a noisy test for a condition that isn't going to be handled anyway).

If it were this (if it would compile):

        weakProperty?.variable = weakProperty?.otherVariable

then I can see another stylistic argument (concisely testing a condition "inline").

But this:

        weakProperty?.variable = weakProperty!.otherVariable

has the appearance of using "!" simply to shut the compiler up, or treating optionals as a nuisance quirk of Swift that is best ignored. That may not be the intention, but it reads that way. We've been there already: Resolved: Insert "!" is a bad fixit

2 Likes

The "stylistic argument" that was discussed was the idea that if the method or accessor resolved - that is, the weak property was indeed non-nil, and therefore the execution continued, then within that access you could assume that the object was itself non-nil due to fact it would not have gotten to that stage otherwise.

The example people used and I believe the ultimate intent of the discussion was people trying to force their closures to be single line and avoid a guard let self = self..., eg:

dismissViewController(animated: true) { [weak self] in
    self?.delegate?.object(self!, didUpdateState: self!.state)
}

This would avoid the separate guard statement:

dismissViewController(animated: true) { [weak self] in
    guard let self = self else { return }
    self.delegate?.object(self, didUpdateState: self.state)
}

The discussion was that this was needlessly messy considering that the compiler did enforce the order of operations to ensure the safety of the access. (which I’m now aware it doesn’t)

I couldn't find any references online to how Swift officially handles this and whether there was indeed a rule guiding compiler development to avoid this ever being accidentally "broken" in the future.

Personally I prefer to resolve optionals explicitly wherever possible, but I thought understanding the order of operations and guarantees was worth a little investigation.

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.

5 Likes

That clarification is helpful, because it makes it clear that what would be really needed to support that closure style is "optional statement execution", as a sort of extension to optional chaining. That is, you'd want a way to write an arbitrary statement so that it would not execute if any optional chain within it can't execute (or something like that).

That might actually be quite a cool feature — a sort of unary ??.

However, simulating that feature by relying on the order of evaluation (assuming it was fixed by the language, and not subject to change by optimization, and …, and …) seems likely to make the code much harder to read (the reader would have to be able to do the mental gymnastics to work out the order of execution), and would be stylistically undesirable for that reason.

Which of the two stylistic considerations should "win" would be a decision you'd have to take, but I know I'd opt for the "guard" statement.

I sometimes wish we could use let in ternary conditionals, so that:

let y = (let x = x) ? foo(x) : 0

would be equivalent to:

let y = (x != nil) ? foo(x!) : 0

or:

let y: Int

if let x = x
{
    y = foo(x)
}
else
{
   y = 0
}

Jeremy

let y = x.map({ foo($0) }) ?? 0

Thanks!

I need to get my head around using map with optionals (not just arrays).

Jeremy