How are observable variables implemented efficiently “under the hood”, i.e. at a low level, by the compiler?
I’m merely curious about the general approach, not detailed code.
How are observable variables implemented efficiently “under the hood”, i.e. at a low level, by the compiler?
I’m merely curious about the general approach, not detailed code.
Observables are implemented using property delegation:
https://kotlinlang.org/docs/reference/delegated-properties.html
The getter and setter get delegated to a special class that implements some sort of behavior (lazy, obeservable, etc). In most cases they are just a light weight class with a property storing the data and a special getter and optional setter function.
When you use a delegate the compiler creates a field with the delegate class and the getter/setter just call that class.
So, in effect, when an observable “x” is modified, e.g., “x = y”, the compiler generates the code needed to notify its watcher(s) of the event?
class Foo {
val foo by Delegates.observable("foo") { old, new -> println("$old -> $new") }
}
This code gets compiled to something that is similar to this
class Foo {
private val foo$delegate = Delegates.observable("foo") { old, new -> println("$old -> $new") }
val foo: String
set(value) { foo$delegate.setValue(this, this::foo, value) }
get() = foo$delegate.getValue(this, this::foo)
}
The delegate is implemented in the files I linked above, but it is pretty basic and just calls the callback whenever the setter is called.
Thanks. But I’m still struggling to understand delegation. I was looking for a more generic explanation, without language-specific features or code details.
fun <T>Delegates.observable(initValue: T, onChange: (T, T) -> Unit) = object : ReadWriteProperty {
private val value = initValue
fun setValue(newValue: T) {
val old = value
value = newValue
onChange.invoke(old, newValue)
}
fun getValue(): T = value
}
class Foo {
private val fooDelegate = Delegates.observable("foo") { old, new ->
println("$old -> $new")
}
val foo: String
set(value) { fooDelegate.setValue(value) }
get() = fooDelegate.getValue()
}
val foo = Foo()
println(foo.foo) // prints "foo"
foo.foo = "bar" // prints "foo -> bar"
println(foo.foo) // prints "bar"
The Foo
class is a simplified version of what the kotlin compiler generates when you use the code from my first example.
I can’t really simplify the code more than that. When you call foo.foo
it calls the getter of the delegate instance, same for foo.foo = "bar"
. The setter just calls the lambda you pass to the delegate in the first place.
Is it possible to designate a variable “foo” as observed, and then simply do “foo = bar”?
Wasabi375, thanks for your help. I think I understand.
Another way to think about it is that a property is nothing but a field, a getter, and a setter, so whenever you call foo = bar
, you are actually calling setFoo(bar)
, the setFoo
method can do absolutely anything that it wants, but in this example, it just calls the delegate’s setValue
method.