Skip to content

Instantly share code, notes, and snippets.

@helje5
Created September 16, 2023 13:16
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 helje5/0a4eb12fcb7ca22b17e0d8c469aa3f26 to your computer and use it in GitHub Desktop.
Save helje5/0a4eb12fcb7ca22b17e0d8c469aa3f26 to your computer and use it in GitHub Desktop.
An `Observable` property wrapper that only creates the observable value once
/**
* An Observable property wrapper that only creates the observable
* value once.
*
* Example:
* ```swift
* @Observable class Item {
* }
*
* struct MyView: View {
* @StateObservable var item = Item()
* }
* ```
*/
@propertyWrapper
struct StateObservable<ObjectType: Observable>: DynamicProperty {
final class ObservationBridge: ObservableObject {
let observable : ObjectType
init(_ observable: ObjectType) { self.observable = observable }
}
@StateObject private var bridge : ObservationBridge
init(wrappedValue thunk: @autoclosure @escaping () -> ObjectType) {
_bridge = .init(wrappedValue: ObservationBridge(thunk()))
}
@MainActor var wrappedValue: ObjectType { bridge.observable }
}
@helje5
Copy link
Author

helje5 commented Sep 16, 2023

P.S.: Don't use it, but keep your state outside of views! 😜

@helje5
Copy link
Author

helje5 commented Sep 16, 2023

Related discussion about iOS 17 Observable objects and @State:
https://mastodon.social/@helge/111074594371761479

@helje5
Copy link
Author

helje5 commented Sep 16, 2023

Here is another version that overloads @StateObject, this doesn't work w/ function calls though (would have to qualify), and is also slower in property access:

extension StateObject {

  /**
   * Use Observable's with `@StateObject`.
   *
   * Example:
   * ```swift
   * @Observable class Item {
   * }
   *
   * struct MyView: View {
   *   @StateObject var item = Item()
   * }
   * ```
   */
  init<T>(wrappedValue thunk: @autoclosure @escaping () -> T)
    where T: Observable, ObjectType == ObservationBridge<T>
  {
    self.init(wrappedValue: ObservationBridge(thunk()))
  }
}
@dynamicMemberLookup
final class ObservationBridge<ObjectType>: ObservableObject {
  let observable : ObjectType
  init(_ observable: ObjectType) { self.observable = observable }
  public subscript<V>(dynamicMember keyPath: KeyPath<ObjectType, V>) -> V {
    observable[keyPath: keyPath]
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment