Hanbit the Developer

Kotlin Documentation | Delegated properties 본문

Mobile/Kotlin

Kotlin Documentation | Delegated properties

hanbikan 2023. 5. 23. 16:50

Kotlin Documentation 시리즈에 대해

Category: Concepts - Classes and objects

문서 링크: https://kotlinlang.org/docs/delegated-properties.html


Delegated properties

val/var <property name>: <Type> by <expression>
class Example {
    var p: String by Delegate()
}
class Delegate {
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return "$thisRef, thank you for delegating '${property.name}' to me!"
    }

    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        println("$value has been assigned to '${property.name}' in $thisRef.")
    }
}

Standard delegates

Lazy properties

val lazyValue: String by lazy {
    println("computed!")
    "Hello"
}

fun main() {
    println(lazyValue)
    println(lazyValue)
}
computed!
Hello
Hello

특징:

  • thread-safe
  • 초기화와 사용하는 쓰레드가 언제가 같다면(싱글 쓰레드 환경), LazyThreadSafetyMode.NONE을 사용하여 오버헤드를 제거할 수 있다.

Observable properties

값을 할당할 때마다 블럭이 호출됨

class User {
    var name: String by Delegates.observable("<no name>") {
        prop, old, new ->
        println("$old -> $new")
    }
}

fun main() {
    val user = User()
    user.name = "first"
    user.name = "second"
}
<no name> -> first
first -> second

vetoable(): 할당이 발생하기 전에 블럭 호출

Delegating to another property

var topLevelInt: Int = 0
class ClassWithDelegate(val anotherClassInt: Int)

class MyClass(var memberInt: Int, val anotherClassInstance: ClassWithDelegate) {
    var delegatedToMember: Int by this::memberInt
    var delegatedToTopLevel: Int by ::topLevelInt

    val delegatedToAnotherClass: Int by anotherClassInstance::anotherClassInt
}
var MyClass.extDelegated: Int by ::topLevelInt

Storing properties in a map

class User(val map: Map<String, Any?>) {
    val name: String by map
    val age: Int     by map
}

val user = User(mapOf(
    "name" to "John Doe",
    "age"  to 25
))

MutableMap을 사용하여 read & write 모두 수행 가능

class MutableUser(val map: MutableMap<String, Any?>) {
    var name: String by map
    var age: Int     by map
}
println(user.name) // Prints "John Doe"
println(user.age)  // Prints 25

Local delegated properties

fun example(computeFoo: () -> Foo) {
    val memoizedFoo by lazy(computeFoo)

    if (someCondition && memoizedFoo.isValid()) {
        memoizedFoo.doSomething()
    }
}

Property delegate requirements

read-only 프로퍼티(val)에는 operator getValue() 함수를 제공해야 한다.

mutable 프로퍼티(var)에는 operator getValue(), setValue() 함수를 제공해야 한다:

class Resource

class Owner {
    var varResource: Resource by ResourceDelegate()
}

class ResourceDelegate(private var resource: Resource = Resource()) {
    operator fun getValue(thisRef: Owner, property: KProperty<*>): Resource {
        return resource
    }
    operator fun setValue(thisRef: Owner, property: KProperty<*>, value: Any?) {
        if (value is Resource) {
            resource = value
        }
    }
}

delegate를 클래스를 작성하지 않고 anonymous object로 생성할 수 있다. 코틀린 standard library에서 제공하는 ReadOnlyProperty, ReadWriteProperty 인터페이스를 사용하면 된다:

fun resourceDelegate(resource: Resource = Resource()): ReadWriteProperty<Any?, Resource> =
    object : ReadWriteProperty<Any?, Resource> {
        var curValue = resource
        override fun getValue(thisRef: Any?, property: KProperty<*>): Resource = curValue
        override fun setValue(thisRef: Any?, property: KProperty<*>, value: Resource) {
            curValue = value
        }
    }

val readOnlyResource: Resource by resourceDelegate()  // ReadWriteProperty as val
var readWriteResource: Resource by resourceDelegate()

Translation rules for delegated properties

Providing a delegate

provideDelegate operator를 정의함으로써 객체 생성 로직을 확장할 수 있다:

class ResourceDelegate<T> : ReadOnlyProperty<MyUI, T> {
    override fun getValue(thisRef: MyUI, property: KProperty<*>): T { ... }
}

class ResourceLoader<T>(id: ResourceID<T>) {
    operator fun provideDelegate(
            thisRef: MyUI,
            prop: KProperty<*>
    ): ReadOnlyProperty<MyUI, T> {
        checkProperty(thisRef, prop.name)
        // create delegate
        return ResourceDelegate()
    }

    private fun checkProperty(thisRef: MyUI, name: String) { ... }
}

class MyUI {
    fun <T> bindResource(id: ResourceID<T>): ResourceLoader<T> { ... }

    val image by bindResource(ResourceID.image_id)
    val text by bindResource(ResourceID.text_id)
}