Binding<T> accesses wrapped value on init

Hi all!
I've noticed this today, and I'm not sure it's correct behaviour.
Though in most cases it doesn't cause any trouble, in one particular case it can.
Let's say we have to have a binding to an optional which is nil at a time the Binding to it is being initialised:

import SwiftUI

struct Test{
    var x: Int?
    var binding: Binding<Int>{
        Binding<Int>(
            get: { self.x! }, set: { _ in })
    }
}

let t = Test()
let binding = t.binding

This code crashes, though, probably it shouldn't, since the wrapped value was not accessed intentionally. I know that this may be worked around in an obvious way by converting 'binding' into optional and unwrapping it later.
But anyway, seems like the 'get' closure should be called only to access the wrapped value, not on init... unless I miss something.

1 Like

As a SwiftUI bug, it's probably not appropriate for this forum, but that's interesting!

A similar non-SwiftUI wrapper won't have the same issue. (This will not help you, because you surely need a Binding.)

@propertyWrapper public struct Computed<Value> {
  public typealias Get = () -> Value
  public typealias Set = (Value) -> Void

  public init(
    get: @escaping Get,
    set: @escaping Set
  ) {
    self.get = get
    self.set = set
  }

  public var get: Get
  public var set: Set

  public var wrappedValue: Value {
    get { get() }
    nonmutating set { set(newValue) }
  }

  public var projectedValue: Self {
    get { self }
    set { self = newValue }
  }
}
1 Like

I guess it is accessed somehow. Try to put a breakpoint in the getter and check the call stack

1 Like

If you look at the declaration of Binding in SwiftUI's .swiftinterface file, you'll see that it's memory layout looks like this:

@frozen @propertyWrapper @dynamicMemberLookup public struct Binding<Value> {
  public var transaction: SwiftUI.Transaction
  internal var location: SwiftUI.AnyLocation<Value>
  fileprivate var _value: Value
  …

So Binding has a stored property of type Value (note: not an Optional), which it has to initialize at init. Given this, it makes sense that it has to call the getter.

I can't explain why Binding is implemented like this.

The .swiftinterface file can be found at e.g. /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/System/Library/Frameworks/SwiftUI.framework/Modules/SwiftUI.swiftmodule/arm64-apple-ios.swiftinterface

Edit: .swiftinterface files don’t generally show the memory layout of a type, i.e. you can't necessarily tell the difference between stored and computed properties, nor will you see internal/private APIs. I think this is different for Binding because it's @frozen.

3 Likes

Thanks! I actually use Binding even outside SwiftUI, since it's there and it's a nice concept.
By the way, is there a dedicated SwiftUI forum where I could brag about such things?

It definitely tries to access the optional, since it crashes with "Unexpectedly found nil..".

Wow! Thanks, but this is a little bit too deep for me! I just wanted to point out that it feels like the behaviour of Binding is not quite correct. Not sure if I will be able to correct it myself though :-)

I didn't mean it like that. My post is an explanation why Binding behaves the way it does, and it also shows IMO that the behavior won't be considered a bug by the SwiftUI team.

And even if it were seen as a bug, it would be unfixable because the _value property needs to be initialized with something, and the memory layout of the Binding type can never change because it's frozen.

1 Like

Now I see! Thank you! :pray: