Created
April 1, 2024 16:28
-
-
Save tciuro/0f423b3acdc0363a300e3ba08d7530a1 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import SwiftUI | |
@Observable | |
final class AppModel { | |
var value = 0 | |
func setRandomValue() { | |
value = Int.random(in: 0 ... 10) | |
} | |
} | |
extension InjectedValues { | |
var appModel: AppModel { | |
get { Self[AppModelKey.self] } | |
set { Self[AppModelKey.self] = newValue } | |
} | |
} | |
private struct AppModelKey: InjectionKey { | |
static var currentValue = AppModel() | |
} | |
struct FooBar { | |
@Injected(\.appModel) var appModel: AppModel | |
func printAppModelFoo() { | |
print("FooBar printAppModelFoo: \(appModel.value)") | |
} | |
} | |
final class ContentViewModel: ObservableObject { | |
@Injected(\.appModel) var appModel: AppModel | |
func printAppModelAddress() { | |
print("ContentViewModel printAppModelAddress: \(addressOf(appModel))") | |
} | |
func addressOf(_ o: some AnyObject) -> String { | |
let addr = unsafeBitCast(o, to: Int.self) | |
return String(format: "%p", addr) | |
} | |
} | |
struct ContentView: View { | |
@Injected(\.appModel) var appModel: AppModel | |
@State private var contentViewModel = ContentViewModel() | |
@State private var fooBar = FooBar() | |
var body: some View { | |
VStack(spacing: 20) { | |
Image(systemName: "globe") | |
.imageScale(.large) | |
.foregroundStyle(.tint) | |
Text("\(appModel.value)") | |
Text("\(contentViewModel.addressOf(appModel))") | |
Button("Next") { | |
appModel.setRandomValue() | |
contentViewModel.printAppModelAddress() | |
fooBar.printAppModelFoo() | |
} | |
Text("The address should stay the same\nwhile the value changes") | |
} | |
.padding() | |
} | |
} | |
/** | |
@Injected Implementation | |
*/ | |
public protocol InjectionKey { | |
/// The associated type representing the type of the dependency injection key's value. | |
associatedtype Value | |
/// The default value for the dependency injection key. | |
static var currentValue: Self.Value { get set } | |
} | |
/// Provides access to injected dependencies. | |
struct InjectedValues { | |
/// This is only used as an accessor to the computed properties within extensions of `InjectedValues`. | |
private static var current = InjectedValues() | |
/// A static subscript for updating the `currentValue` of `InjectionKey` instances. | |
static subscript<K>(key: K.Type) -> K.Value where K: InjectionKey { | |
get { key.currentValue } | |
set { key.currentValue = newValue } | |
} | |
/// A static subscript accessor for updating and references dependencies directly. | |
static subscript<T>(_ keyPath: WritableKeyPath<InjectedValues, T>) -> T { | |
get { current[keyPath: keyPath] } | |
set { current[keyPath: keyPath] = newValue } | |
} | |
} | |
@propertyWrapper | |
struct Injected<T> { | |
private let keyPath: WritableKeyPath<InjectedValues, T> | |
var wrappedValue: T { | |
get { InjectedValues[keyPath] } | |
set { InjectedValues[keyPath] = newValue } | |
} | |
init(_ keyPath: WritableKeyPath<InjectedValues, T>) { | |
self.keyPath = keyPath | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks to Jon Duenas for the comments and suggestions:
This works:
Obviously,
@ObservationIgnored
has some intended effects which could affect future implementations, but at least I know now what's going on.