안드로이드 코드를 짜면서 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()
}
Kotlin
복사
여기서 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
}
Kotlin
복사
코틀린에서는 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.")
}
}
Kotlin
복사
프로퍼티 위임은 프로퍼티의 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.
Kotlin
복사
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
Kotlin
복사
첫 println(lazyValue)는 lazy에 전달된 람다를 호출하여 computed가 같이 출력되고 "Hello"를 저장하고, 다음 println(lazyValue)는 저장된 값을 그대로 반환하여 "Hello"만 반환한다.
by viewModels()
val someViewModel: SomeViewModel by viewModels()
Kotlin
복사
프로퍼티 someViewModel은 SomeViewModel타입이며, Accessor를 viewModels()에 위임한다.
/* ActivityViewModelLazy.kt */
@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
}
Kotlin
복사
ActivityViewModelLazy는 Lazy를 구현하여 비슷하게 처음 접근했을 때 ViewModelProvider에서 ViewModel을 가져오며 그 이후에 접근했을 때는 cached에 저장된 ViewModel을 반환한다.