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 | |
} | |
} |
Thanks to Jon Duenas for the comments and suggestions:
Can’t use property wrappers with Observable macro. You’ll have to mark it
@/ObservationIgnored
and then you obviously lose out any any observation benefits.
This works:
@Observable
final class ContentViewModel {
@ObservationIgnored
@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)
}
}
Obviously, @ObservationIgnored
has some intended effects which could affect future implementations, but at least I know now what's going on.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
If in the code above I convert this:
into this:
I get the following errors:
Why is that? According to Apple's documentation Migrating from the Observable Object protocol to the Observable macro, it looks like I should be able to do just that... except that I can't.
What am I missing?