Create an account

Very important

  • To access the important data of the forums, you must be active in each forum and especially in the leaks and database leaks section, send data and after sending the data and activity, data and important content will be opened and visible for you.
  • You will only see chat messages from people who are at or below your level.
  • More than 500,000 database leaks and millions of account leaks are waiting for you, so access and view with more activity.
  • Many important data are inactive and inaccessible for you, so open them with activity. (This will be done automatically)


Thread Rating:
  • 656 Vote(s) - 3.48 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Can a Swift Property Wrapper reference the owner of the property its wrapping?

#1
From within a property wrapper in Swift, can you someone refer back to the instance of the class or struck that owns the property being wrapped? Using `self` doesn't obviously work, nor does `super`.

I tried to pass in `self` to the property wrapper's `init()` but that doesn't work either because `self` on `Configuration` is not yet defined when `@propertywrapper` is evaluated.

My use case is in a class for managing a large number of settings or configurations. If any property is changed, I just want to notify interested parties that *something* changed. They don't really need to know which value just, so use something like `KVO` or a `Publisher` for each property isn't really necessary.

A property wrapper looks ideal, but I can't figure out how to pass in some sort of reference to the owning instance that the wrapper can call back to.

References:

[SE-0258][1]

enum PropertyIdentifier {
case backgroundColor
case textColor
}

@propertyWrapper
struct Recorded<T> {
let identifier:PropertyIdentifier
var _value: T

init(_ identifier:PropertyIdentifier, defaultValue: T) {
self.identifier = identifier
self._value = defaultValue
}

var value: T {
get { _value }
set {
_value = newValue

// How to callback to Configuration.propertyWasSet()?
//
// [self/super/...].propertyWasSet(identifier)
}
}
}

struct Configuration {

@Recorded(.backgroundColor, defaultValue:NSColor.white)
var backgroundColor:NSColor

@Recorded(.textColor, defaultValue:NSColor.black)
var textColor:NSColor

func propertyWasSet(_ identifier:PropertyIdentifier) {
// Do something...
}
}


[1]:

[To see links please register here]

Reply

#2
The answer is yes! See [this answer](

[To see links please register here]

)

Example code for calling ObservableObject publisher with a UserDefaults wrapper:

```
import Combine
import Foundation

class LocalSettings: ObservableObject {
static var shared = LocalSettings()

@Setting(key: "TabSelection")
var tabSelection: Int = 0
}

@propertyWrapper
struct Setting<T> {
private let key: String
private let defaultValue: T

init(wrappedValue value: T, key: String) {
self.key = key
self.defaultValue = value
}

var wrappedValue: T {
get {
UserDefaults.standard.object(forKey: key) as? T ?? defaultValue
}
set {
UserDefaults.standard.set(newValue, forKey: key)
}
}

public static subscript<EnclosingSelf: ObservableObject>(
_enclosingInstance object: EnclosingSelf,
wrapped wrappedKeyPath: ReferenceWritableKeyPath<EnclosingSelf, T>,
storage storageKeyPath: ReferenceWritableKeyPath<EnclosingSelf, Setting<T>>
) -> T {
get {
return object[keyPath: storageKeyPath].wrappedValue
}
set {
(object.objectWillChange as? ObservableObjectPublisher)?.send()
UserDefaults.standard.set(newValue, forKey: object[keyPath: storageKeyPath].key)
}
}
}
```
Reply

#3
My experiments based on :

[To see links please register here]


protocol Observer: AnyObject {
func observableValueDidChange<T>(newValue: T)
}

@propertyWrapper
public struct Observable<T: Equatable> {
public var stored: T
weak var observer: Observer?

init(wrappedValue: T, observer: Observer?) {
self.stored = wrappedValue
}

public var wrappedValue: T {
get { return stored }
set {
if newValue != stored {
observer?.observableValueDidChange(newValue: newValue)
}
stored = newValue
}
}
}

class testClass: Observer {
@Observable(observer: nil) var some: Int = 2

func observableValueDidChange<T>(newValue: T) {
print("lol")
}

init(){
_some.observer = self
}
}

let a = testClass()

a.some = 4
a.some = 6

Reply

#4
You cannot do this out of the box currently.

However, the proposal you refer to discusses this as a future direction in the latest version:

[To see links please register here]


For now, you would be able to use a `projectedValue` to assign `self` to.
You could then use that to trigger some action after setting the `wrappedValue`.

As an example:
```
import Foundation

@propertyWrapper
class Wrapper {
let name : String
var value = 0
weak var owner : Owner?

init(_ name: String) {
self.name = name
}

var wrappedValue : Int {
get { value }
set {
value = 0
owner?.wrapperDidSet(name: name)
}
}

var projectedValue : Wrapper {
self
}
}


class Owner {
@Wrapper("a") var a : Int
@Wrapper("b") var b : Int

init() {
$a.owner = self
$b.owner = self
}

func wrapperDidSet(name: String) {
print("WrapperDidSet(\(name))")
}
}

var owner = Owner()
owner.a = 4 // Prints: WrapperDidSet(a)
```
Reply

#5
The answer is no, it's not possible with the current specification.

I wanted to do something similar. The best I could come up with was to use reflection in a function at the end of `init(...)`. At least this way you can annotate your types and only add a single function call in `init()`.


```

fileprivate protocol BindableObjectPropertySettable {
var didSet: () -> Void { get set }
}

@propertyDelegate
class BindableObjectProperty<T>: BindableObjectPropertySettable {
var value: T {
didSet {
self.didSet()
}
}
var didSet: () -> Void = { }
init(initialValue: T) {
self.value = initialValue
}
}

extension BindableObject {
// Call this at the end of init() after calling super
func bindProperties(_ didSet: @escaping () -> Void) {
let mirror = Mirror(reflecting: self)
for child in mirror.children {
if var child = child.value as? BindableObjectPropertySettable {
child.didSet = didSet
}
}
}
}
```
Reply



Forum Jump:


Users browsing this thread:
1 Guest(s)

©0Day  2016 - 2023 | All Rights Reserved.  Made with    for the community. Connected through