본문 바로가기

프로그래밍/Android

Delegate란?

대리자

(임시저장을 생활화하자.............................ㅠㅠㅠ)

대리자는 어떤 프로퍼티가 작업을 수행할 때, 다른 객체에게 위임하여 그 객체의 메서드가 실행되게 하는 것이다!!

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.'")
    }
}

이런 코드를 생각해보면, Delegate 인스턴스를 위임하는 프로퍼티 p에서 값을 읽으면, Delegate의 getValue() 메서드를 실행한다. 즉 원래 객체의 get() 대신 Delegate의 getValue()메서드가 실행되는 것이다.

val e = Example()
println(e.p) // Example@33a17727, thank you for delegating ‘p’ to me!

때문에 코드가 실행되면 위와 같은 결과가 나온다.
(https://pluu.gitbooks.io/kotlin/content/d074-b798-c2a4-c640-c624-be0c-c81d-d2b8/delegated-properties.html)

Observable

 

public inline fun <T> observable(initialValue: T, 
crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Unit): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) { override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) = onChange(property, oldValue, newValue) }

observable은 프로퍼티의 데이터가 변할 때마다 callback 함수가 실행된다. (onChange)

 

 

import kotlin.properties.Delegates

fun main() {
    var observableField: Int by Delegates.observable(0)
    { property, old, new -> println("old: $old, new: $new") }
    println(observableField)
    observableField = 1
    println(observableField) // 1
}

예시 코드를 작성해보면, 이렇게 observableField 프로퍼티는 Delegates.observable의 대리자를 가진다.
이때 observableField 값이 set() 되면, old:0, new: 1 이 출력된다.

 

Vetoable

 

public inline fun <T> vetoable(initialValue: T, 
crossinline onChange: (property: KProperty<*>, oldValue: T, newValue: T) -> Boolean): ReadWriteProperty<Any?, T> = object : ObservableProperty<T>(initialValue) { override fun beforeChange(property: KProperty<*>, oldValue: T, newValue: T): Boolean = onChange(property, oldValue, newValue) }

Vetoable은 Observable과 유사하지만 return 값이 Boolean 값이고 이를 통해 변경되는 값을 제한할 수 있다.

예시를 보면

var vetoableField: Int by Delegates.vetoable(0) {
property, old, new -> 
println("old: $old, new: $new") 
new % 2 == 0 
} 

fun main() { 
println(vetoableField) // 0
vetoableField = 1  // old: 0, new: 1
println(vetoableField)  // 0
vetoableField = 2  // old: 0, new: 2
println(vetoableField)  // 2
}

 

이처럼, 짝수일 때만 true를 반환하게 설정하여 짝수일 경우만 vetoableField의 값이 변경되는 것을 확인할 수 있다.

 

글이 날라가버려서 너무 두서없어졌다...
마지막으로 원래 목표했던 물음표를 확인하자!!

 

Delegates.notNull

프로젝트를 하며 자동완성 기능을 가끔 썻던 코드.
lateinit과 유사하다.

lateinit은

  • var만 가능, primitive Type(Int, Boolean)은 불가능
  • null 값을 넣을 수 없음
  • Custom Getter/Setter를 만들 수 없음
  • 초기화 전에 사용할 수 없음!

내부 구현은 추가로 학습!!

 

Delegates.notNull은 프로퍼티에 null값을 넣을 수 없게 하는 것이다.

var nonNullString: String by Delegates.notNull<String>()
nonNullString = "Hello World"
println("Non null value is: ${nonNullString}")
nonNullString = null  // 컴파일 에러, non-null 타입에 null을 넣을 수 없음

위와 같은 경우, nonNullString 변수에 null 값을 넣을 수 없다. Delegates.notNull()을 대리자로 받았기 때문이다.

  • primitive Type도 사용 가능
  • 초기화 전 사용 불가
  • null 설정 불가!
  • late init보다 성능이 안좋다 (lateinit 내부구현과 함께 확인하자)
// notNull()의 내부 코드

public fun <T : Any> notNull(): ReadWriteProperty<Any?, T> = NotNullVar()


private class NotNullVar<T : Any>() : ReadWriteProperty<Any?, T> {
    private var value: T? = null

    public override fun getValue(thisRef: Any?, property: KProperty<*>): T {
        return value ?: throw IllegalStateException("Property ${property.name} should be initialized before get.")
    }

    public override fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
        this.value = value
    }
}

notNull()은 ReadWriteProPerty 자료형을 가진다.
lateinit과 다르게 코드로 예외처리를 하는 것이 아닌, ReadWriteProPerty 안에 선언한 Type을 감싸고 있는 구조이다.

그래서 primitive type을 사용할 수 있다.
그리고 코드의 구현은 단순히 초기값(getValue)와 변경값(setValue)가 null인지 throw문을 통해 예외처리를 한다!

 

이런 차이에서 Delegates.notNull()이 오버헤드가 커진다. (이유는...?) (더 무겁게 동작한다?)