Anand Kore
Anand Kore

Reputation: 1330

Is it possible to add an observer on struct variable in Swift?

I need to track the update in a variable of struct type. Is it possible to add an observer on struct variable in Swift?

Example:

struct MyCustomStruct {
    var error:Error?
    var someVar:String?
}

class MyClass{
  var myCustomStruct:MyCustomStruct?
}

I want to add an observer on myCustomStruct variable.

Upvotes: 5

Views: 7589

Answers (5)

Alejandro L.Rocha
Alejandro L.Rocha

Reputation: 1145

I am not sure if this might be a really good answer or not:

But you can do something like

final public class WrapperStruct<T>: ObservableObject {
    @Published public var value: T
    public init(_ value: T) {
        self.value = value
    }
}

And then just called create your struct like:

@ObservedObject private var myStruct: WrapperStruct<MyStruct>

https://www.swiftjectivec.com/observing-structs-swiftui/

I have my doubts if this is a good approach but seems to be working.

Upvotes: 0

Rob
Rob

Reputation: 437622

The standard Swift “property observers” (didSet and willSet) are designed to let a type observe changes to its own properties, but not for letting external objects add their own observers. And KVO, which does support external observers, is only for dynamic and @objc properties NSObject subclasses (as outlined in Using Key-Value Observing in Swift).

So, if you want to have an external object observe changes within a struct, as others have pointed out, you have to create your own observer mechanism using Swift didSet and the like. But rather than implementing that yourself, property by property, you can write a generic type to do this for you. E.g.,

struct Observable<T> {
    typealias Observer = String

    private var handlers: [Observer: (T) -> Void] = [:]

    var value: T {
        didSet {
            handlers.forEach { $0.value(value) }
        }
    }

    init(_ value: T) {
        self.value = value
    }

    @discardableResult
    mutating func observeNext(_ handler: @escaping (T) -> Void) -> Observer {
        let key = UUID().uuidString as Observer
        handlers[key] = handler
        return key
    }

    mutating func remove(_ key: Observer) {
        handlers.removeValue(forKey: key)
    }
}

Then you can do things like:

struct Foo {
    var i: Observable<Int>
    var text: Observable<String>

    init(i: Int, text: String) {
        self.i = Observable(i)
        self.text = Observable(text)
    }
}

class MyClass {
    var foo: Foo

    init() {
        foo = Foo(i: 0, text: "foo")
    }
}

let object = MyClass()
object.foo.i.observeNext { [weak self] value in   // the weak reference is really only needed if you reference self, but if you do, make sure to make it weak to avoid strong reference cycle
    print("new value", value)
}

And then, when you update the property, for example like below, your observer handler closure will be called:

object.foo.i.value = 42

It’s worth noting that frameworks like Bond or RxSwift offer this sort of functionality, plus a lot more.

Upvotes: 12

Robert Dresler
Robert Dresler

Reputation: 11150

With variables you can use two default observers

  • willSet - represents moment before variable will be set with new value

  • didSet - represents moment when variable was set


Also in observer you can work with two values. With current variable in current state, and with constant depending on observer

struct Struct {
    var variable: String {
        willSet {
            variable  // before set 
            newValue  // after set,  immutable
        }
        didSet {
            oldValue  // before set, immutable
            variable  // after set
        }
    }
}

And the same you can do for any other stored property, so you can use it for struct variable in your class too

class Class {
    var myStruct: Struct? {
        didSet {
            ...
        }
    }
}

Also you can for example in did set observer of variable post notification with certain name

didSet {
    NotificationCenter.default.post(name: Notification.Name("VariableSet"), object: nil)
}

and then you can add certain class as observer for notification with this name

class Class {
    init() {
        NotificationCenter.default.addObserver(self, selector: #selector(variableSet), name: Notification.Name("VariableSet"), object: nil)
    }

    deinit {
        NotificationCenter.default.removeObserver(self, name: Notification.Name("VariableSet"), object: nil)
    }

    @objc func variableSet() {
        ...
    }
}

Upvotes: 6

Dasem
Dasem

Reputation: 440

struct MyCustomStruct {
    var error:Error?
    var someVar:String?
}

class MyClass{
    var myCustomStruct:MyCustomStruct? {
        didSet{
            print("my coustomeSruct changed")
        }
    }
}

let aClass = MyClass()
aClass.myCustomStruct?.someVar = " test"
 //prints:my coustomeSruct changed

Upvotes: -3

Vasilis D.
Vasilis D.

Reputation: 1456

Try this, first create a struct with an action variable and when you create an object of the struct set the action parameter on the action you want. ex.

struct testStruct {
var action: (()->())?
var variable: String? {
    didSet {
        self.action?()
    }
}

}

And inside your main code - main class

var testS = testStruct()
    testS.action = {
        print("Hello")
    }
    testS.variable = "Hi"

When you set the testS.variabe = "Hi" it will call the print("Hello")

Upvotes: 0

Related Questions