I’m trying to use a delegated property to store the value for a class property somewhere else. I also want to easily set an initial value for the property at the same time but can’t seem to do so without a memory leak. Sample code (playground):
import kotlin.reflect.KProperty
fun main() {
val a = A() // Memory leak in A?
println( a.stringValue ) // "initial"
}
class A {
lateinit var propertyStore: String
val stringValue: String by Delegate( "initial" )
}
class Delegate( val initialValue: String /* Unnecessary use of memory? */ ) {
operator fun provideDelegate( thisRef: A, property: KProperty < * > ): Delegate {
thisRef.propertyStore = initialValue
return this
}
operator fun getValue( thisRef: A, property: KProperty < * > ): String {
return thisRef.propertyStore
}
operator fun setValue( thisRef: A, property: KProperty < * >, value: String ) {
thisRef.propertyStore = value
}
}
Kotlin provides the provideDelegate method to run code when the delegate is initialised, so the value of the property can be initialised there. But the only means of providing the initial value to that method seems to be storing it as a property of the delegate via the delegate’s constructor. This property then would appear takes up memory unnecessarily within the delegate.
One work-around that seemed possible was to call setValue in an init method of the delegate. But there would seem to be no straightforward way of passing the relevant KProperty to the init method to enable it to call setValue.
Is there another approach to do this without a memory leak?
You can make sure that Delegate("initial") already has access to A when it is called and so it can set it immediately like this (playground):
import kotlin.reflect.KProperty
fun main() {
val a = A()
println( a.stringValue )
}
class A {
lateinit var propertyStore: String
var stringValue: String by Delegate( "initial" )
}
fun A.Delegate(initialValue: String) = Delegate.also { propertyStore = initialValue }
object Delegate {
operator fun getValue( thisRef: A, property: KProperty < * > ): String {
return thisRef.propertyStore
}
operator fun setValue( thisRef: A, property: KProperty < * >, value: String ) {
thisRef.propertyStore = value
}
}
Also, I believe that this isn’t usually called a memory leak, but rather just redundant memory use. Memory leak usually is when some object “escapes” its scope and is held around for longer, which makes it not eligible for garbage collection, therefore making the object stay around in memory.
Another solution is to have a separate delegate provider and the delegate itself:
class A {
lateinit var propertyStore: String
val stringValue: String by DelegateProvider( "initial" )
}
class DelegateProvider(val initialValue: String) {
operator fun provideDelegate(thisRef: A, property: KProperty<*>): Delegate {
thisRef.propertyStore = initialValue
return Delegate
}
}
object Delegate {
operator fun getValue( thisRef: A, property: KProperty < * > ): String {
return thisRef.propertyStore
}
operator fun setValue( thisRef: A, property: KProperty < * >, value: String ) {
thisRef.propertyStore = value
}
}
Although… I’m not sure if we really should use delegates to initialize objects. It seems weird to me.
Also, I understand you provided a simplified example and not your real case, but because this Delegate can be used only with class A and only with its propertyStore prop, then how is it different than to just create a getter and setter for stringValue and don’t use delegates at all?
Ah, that’s brilliant. Thanks both for your help. I didn’t realise that you could pass any expression as the second argument of by, so there is room to run any code in between that point and returning the eventual delegate.
My reason for delegating is nothing much more exciting than the “storing an object’s properties in a map”: I want to access the properties by means of my own reference scheme as well as in regular code.
I am going to use the second solution of a provider object because that can also allow the delegate access to the KProperty<*> object on initialisation, which I realised I needed to get all the information I wanted at that point in the code.