Can the Swift adaptation of KVO handle custom targets? - swift

To do KVO in Swift, you make a NSObject-based #objc class, and can observe properties that are stored and marked #objc and dynamic.
Can you observe on computed properties (that are #objc and dynamic)?
I just glanced at the Objective-C guide, and it showed you could mark KVO dependencies for a computed property. And (I think) observe on an array property changing at the element level. Can either of those be done in Swift?

You asked:
Can you observe on computed properties (that are #objc and dynamic)?
Yes. Just mark your computed property #objc and dynamic and when the setter for the computed property is called, then the observer will be called.
For example, consider a synchronizer class, where you want to observe the computed property, value :
class Synchronized: NSObject {
private var _value: Int
private let queue = DispatchQueue(label: "synchronizer", attributes: .concurrent)
init(value: Int) {
_value = value
super.init()
}
#objc dynamic var value: Int {
get { queue.sync { _value } }
set { queue.async(flags: .barrier) { self._value = newValue} }
}
}
Here, the value is a computed variable, which you can set and all the usual KVO can take place:
token = foo.observe(\.value, options: .new) { _, change in
print(change.newValue ?? "Unknown")
}
This observer is triggered when the value setter is called.
The only trick is going to be if you want the KVO triggered notification if the the value returned by the computed property changes via something other than the computed property’s setter. In that case, you’d have to manually issue the KVO notifications.

Related

How to attach subscriber to State or Binding in SwiftUI/Combine?

With a Combine Publisher, I can use the following to call a closure whenever a value changes:
let cancellable = x.sink { value in … }
How can I achieve the same behaviour with a variable marked #State or #Binding?
Update
The below answer doesn't seem to work anymore, instead one can use .onChange(of:) instead on the property
.onChange(of: someProperty) { value in
//use new value for someProperty
}
You can use willSet and didSet as with any normal property
#State private var someProperty: String {
didSet {
}
willSet {
}
}

swift didSet not called in a singleton

Lets say I have a singleton Manager
class Manager {
static let sharedInstance = Manager()
var text: String {
didSet (value) { print("didSet \(value)") }
}
init () {
self.text = "hello"
}
}
If I do
Manager.sharedInstance.text = "world"
text is still 'hello'
but if I do it twice, the second time it is world
It is working fine.
The behaviour your experienced is explained by 2 facts
Fact 1
As Apple says didSet (and willSet as well) is not called during the init.
The willSet and didSet observers provide a way to observe (and to
respond appropriately) when the value of a variable or property is
being set. The observers are not called when the variable or property
is first initialized. Instead, they are called only when the value is
set outside of an initialization context.
Fact 2
The parameter of didSet does refer to the old value, so you should
rename value as oldValue.
use text in the print
So from this
didSet (value) { print("didSet \(value)") }
to this
didSet (oldValue) { print("didSet \(text)") }
Instead in your code you are printing the old value (the one that has been overwritten).

In Swift, didSet doesn’t fire when invoked from init()

I’ve got a car and a driver. They mutually reference each other. In the car’s init() I create a driver and assign it to the driver member. The driver member has a didSet method which is supposed to set the driver’s car, thus mutually link them to each other.
class GmDriver {
var car: GmCar! = nil
}
class GmCar {
var driver: GmDriver {
didSet {
driver.car = self
}
}
init() {
driver = GmDriver()
}
}
let myCar = GmCar()
println(myCar.driver.car) // nil
However, the didSet never fires. Why?
Apple Documentation:
The willSet and didSet observers of superclass properties are called when a property is set in a subclass initializer, after the superclass initializer has been called. They are not called while a class is setting its own properties, before the superclass initializer has been called.
init() {
defer {
driver = GmDriver()
}
}

Swift property - getter ivar

Is there an ivar property we should use in a Swift getter? My code is causing the getter to call the getter until the program crashes:
var document: UIDocument? {
get {
return self.document
}
set {
self.document = newValue
useDocument()
}
}
Swift properties do not have the concept of separate, underlying storage like they do in Objective-C. Instead, you'll need to create a second (private) property and use that as the storage:
private var _document: UIDocument?
var document: UIDocument? {
get {
return _document
}
set {
_document = newValue
useDocument()
}
}
If all you're trying to do is call useDocument() after the document property is set, you can omit the getter, setter, and private property and instead just use willSet or didSet.
If what you are trying to achieve is add some custom processing when the property is set, you don't need to define a separate backing data member and implement a computed property: you can use the willSet and didSet property observers, which are automatically invoked respectively before and after the property has been set.
In your specific case, this is how you should implement your property:
var document: UIDocument? {
didSet {
useDocument()
}
}
Suggested reading: Property Observers
In your code there is an infinite recursion situation: for example, self.document inside the getter keep calling the getter itself.
You need to explicitly define an ivar yourself. Here is a possible solution:
private var _document:UIDocument?
var document: UIDocument? {
get {
return self._document
}
set {
self._document = newValue
useDocument()
}
}

Swift implement setter without getter

How do you implement setter without getter in Swift? I need to call a method when the value is set:
var selectedIndex : Int{
set {
selectItemAtIndex(newValue)
}
}
but in Swift, you are required to use both getter and setter, not just one.
You can use the property observer didSet. This will be called immediately after setting the value.
var selectedIndex: Int {
didSet {
selectItemAtIndex(selectedIndex)
}
}