Kotlin ‘by’ 키워드
안드로이드 코드를 짜면서 by lazy
, by viewModels()
를 쓸 때 by라는 키워드를 사용했는데 이것이 어떤 역할을 하는지 작성해보려고 한다.
위임 패턴
객체가 요청을 다른 객체(helper object)에 위임해서 처리하는 패턴이다.
class Rectangle(val width: Int, val height: Int) {
fun area() = width * height
}
class Window(val bounds: Rectangle) {
//위임
fun area() = bounds.area()
}
여기서 Window
클래스는 area
메서드로 호출되는 요청을 인자로 갖고 있는 Rectangle
객체에 위임하고 있다.
클래스 위임
interface ClosedShape {
fun area(): Int
}
class Rectangle(val width: Int, val height: Int) : ClosedShape {
override fun area() = width * height
}
class Circle(val radius: Int) : ClosedShape {
override fun area() = radius * radius * 3
}
class Window(private val bounds: ClosedShape) : ClosedShape by bounds
fun main() {
val circleWindow = Window(Circle(5))
val rectangleWindow = Window(Rectangle(4, 5))
println(circleWindow.area()) //prints 75
println(rectangleWindow.area()) //prints 20
}
코틀린에서는 by
키워드를 사용하여 언어 차원에서 위임 패턴을 구현할 수 있다. Window
클래스는 ClosedShape
를 구현하며, by
키워드로 인해 인자로 전달된 bounds
가 구현한 override
메서드들을 사용할 수 있게 된다.
프로퍼티 위임
import kotlin.reflect.KProperty
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.")
}
}
프로퍼티 위임은 프로퍼티의 Accessor(get
, set
)을 delegate
에 위임하는 것이다. 위의 코드에서 프로퍼티 p: String
은 Delegate
에 getValue
와 setValue
를 이임하고, Delegate
클래스 내에 각각 오버로드돼있다.
val example = Example()
println(example.p) // prints Example@33a17727, thank you for delegating 'p' to me!
example.p = "NEW" // prints NEW has been assigned to 'p' in Example@33a17727.
p
를 읽으면 get
이 Delegate
에 위임돼있으므로 getValue
메서드가 호출된다. p
를 수정하면 set
이 Delegate
에 위임돼있으므로 setValue
메서드가 호출된다. 각각 내부의 String
이 리턴돼 위 코드의 주석처럼 출력된다.
by lazy
lazy()
메서드는 람다를 인자로 받아 Lazy<T>
인스턴스를 반환한다. get()
의 첫 호출은 lazy()
에 전달된 람다를 기억하고 그 후의 get()
호출은 기억한 값을 그대로 반환한다.
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
fun main() {
println(lazyValue)
println(lazyValue)
}
// computed!
// Hello
// Hello
첫 println(lazyValue)
는 lazy에
전달된 람다를 호출하여 computed가
같이 출력되고 "Hello"
를 저장하고, 다음 println(lazyValue)
는 저장된 값을 그대로 반환하여 "Hello"
만 반환한다.
by viewModels()
val someViewModel: SomeViewModel by viewModels()
프로퍼티 someViewModel은
SomeViewModel
타입이며, Accessor를 viewModels()
에 위임한다.
@MainThread
inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
factory: ViewModelProvider.Factory? = null
): Lazy<VM> = ActivityViewModelLazy(this, VM::class, factory)
/**
* An implementation of [Lazy] used by [ComponentActivity.viewModels] tied to the given [activity],
* [viewModelClass], [factory]
*/
class ActivityViewModelLazy<VM : ViewModel>(
private val activity: ComponentActivity,
private val viewModelClass: KClass<VM>,
private val factory: ViewModelProvider.Factory?
) : Lazy<VM> {
private var cached: VM? = null
override val value: VM
get() {
var viewModel = cached
if (viewModel == null) {
val application = activity.application
?: throw IllegalArgumentException("ViewModel can be accessed " +
"only when Activity is attached")
val resolvedFactory = factory ?: AndroidViewModelFactory.getInstance(application)
viewModel = ViewModelProvider(activity, resolvedFactory).get(viewModelClass.java)
cached = viewModel
}
return viewModel
}
override fun isInitialized() = cached != null
}
ActivityViewModelLazy.kt
ActivityViewModelLazy
는 Lazy
를 구현하여 비슷하게 처음 접근했을 때 ViewModelProvider
에서 ViewModel
을 가져오며 그 이후에 접근했을 때는 cached
에 저장된 ViewModel
을 반환한다.
Comments