Setting Observer for Swift Objects/Properties

Setting Observer for Swift Objects/Properties

You can observe the NSApplication.didChangeScreenParametersNotification notification. This example will only print once each time a display is either connected or disconnected, and what the change was in the number of screens.

Code:

class EventObserverDemo {
var lastCount = NSScreen.screens.count

init() {
NotificationCenter.default.addObserver(
self,
selector: #selector(trigger),
name: NSApplication.didChangeScreenParametersNotification,
object: nil
)
}

@objc private func trigger(notification: NSNotification) {
let newCount = NSScreen.screens.count

if newCount != lastCount {
print("Switched from \(lastCount) to \(newCount) displays")
lastCount = newCount
}
}
}

You don't need to remove/invalidate the observer either, easier to let the system handle it:

If your app targets iOS 9.0 and later or macOS 10.11 and later, you do not need to unregister an observer that you created with this function. If you forget or are unable to remove an observer, the system cleans up the next time it would have posted to it.

Swift property observers, initial value

As Hamish's comment points out, in the case of willSet there's not a valid value that state could have here (and in the case of didSet, there's not a valid value the newValue argument could have).

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

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.



Related Topics



Leave a reply



Submit