Skip to content

Instantly share code, notes, and snippets.

@tciuro
Created April 1, 2024 16:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tciuro/0f423b3acdc0363a300e3ba08d7530a1 to your computer and use it in GitHub Desktop.
Save tciuro/0f423b3acdc0363a300e3ba08d7530a1 to your computer and use it in GitHub Desktop.
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
}
}
@tciuro
Copy link
Author

tciuro commented Apr 1, 2024

If in the code above I convert this:

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)
    }
}

into this:

@Observable
final class ContentViewModel {
    @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)
    }
}

I get the following errors:

Property wrapper cannot be applied to a computed property.
Invalid redeclaration of synthesized property '_appModel'
Ambiguous reference to member '_appModel'

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?

@tciuro
Copy link
Author

tciuro commented Apr 1, 2024

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