SwiftUI.Entry macro only creating computed default values instead of stored?

https://gist.github.com/vanvoorden/37ff2b2f9a2a0d0657a3cc5624cc9139

Hi! I'm experimenting with the Entry macro in a SwiftUI app. I'm a little confused about how to stored a defaultValue to prevent extra work from creating this more than once.

A "legacy" approach to defining an Environment variable looks something like this:

struct StoredValue {
  var value: String {
    "Hello, world!"
  }
  
  init() {
    print("StoredValue.init()")
  }
}

extension EnvironmentValues {
  var storedValue: StoredValue {
    get {
      self[StoredValueKey.self]
    }
    set {
      self[StoredValueKey.self] = newValue
    }
  }
  
  struct StoredValueKey: EnvironmentKey {
    static let defaultValue = StoredValue()
  }
}

The defaultValue is a static stored property.

Here is a "modern" approach using the Entry macro:

struct ComputedValue {
  var value: String {
    "Hello, world!"
  }
  
  init() {
    print("ComputedValue.init()")
  }
}

extension EnvironmentValues {
  @Entry var computedValue: ComputedValue = ComputedValue()
}

From the perspective of the product engineer, it looks like I am defining another stored defaultValue property… but this actually expands to a computed property:

extension EnvironmentValues {
  var computedValue: ComputedValue {
    get {
      self[__Key_computedValue.self]
    }
    set {
      self[__Key_computedValue.self] = newValue
    }
  }
  
  private struct __Key_computedValue: SwiftUICore.EnvironmentKey {
    
    static var defaultValue: ComputedValue {
      get {
        ComputedValue()
      }
    }
  }
  
}

If I tried to use both of these Environment properties in a SwiftUI component, it looks like I can confirm the computedValue is computing its defaultValue several times:

@main
struct EnvironmentDemoApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
    }
  }
}

struct ContentView: View {
  @Environment(\.computedValue) var computedValue
  @Environment(\.storedValue) var storedValue
  
  var body: some View {
    VStack {
      Image(systemName: "globe")
        .imageScale(.large)
        .foregroundStyle(.tint)
      Text("Hello, world!")
    }
    .padding()
  }
}

And then when I run the app:

ComputedValue.init()
StoredValue.init()
ComputedValue.init()
ComputedValue.init()
ComputedValue.init()
ComputedValue.init()
ComputedValue.init()
ComputedValue.init()
ComputedValue.init()

Is there any way to use the Entry macro in a way that we store the defaultValue instead of computing it on-demand every time?

I see the exact same behavior. And the defaultValue is even computed when the Environment is overridden with a new value.

This simple app:

import SwiftUI

struct SomeValue {
    init() {
        print("SomeValue.init()")
    }
}

extension EnvironmentValues {
    @Entry var someValue: SomeValue = SomeValue()
}

struct ContentView: View {
    @Environment(\.someValue) private var someValue

    var body: some View {
        VStack {
            Text("Hello, world!")
        }
        .padding()
    }
}

@main
struct MyAppApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(\.someValue, SomeValue())
        }
    }
}

Will print the following:

SomeValue.init()
SomeValue.init()
SomeValue.init()
SomeValue.init()

And if you put a break point inside the initializer of SomeValue, you will notice that the first time SomeValue is initialized is when it is passed to the .environment() modifier. The succeeding three times is from when the defaultValue is computed.

Even without the use of the Entry macro, the defaultValue is always called/computed even if the value has been explicitly set with the .environment() modifier before it is ever being used.

Though this might be completely by design, it is a bit unexpected (to me at least).

SwiftUI.Entry macro only creating computed default values instead of stored?
 
 
Q