- 作者:老汪软件技巧
- 发表时间:2024-11-24 10:04
- 浏览量:
在 Kotlin 中,属性委托是一种强大的功能,它允许我们将属性的 getter 和 setter 委托给另一个对象。这种机制使得我们可以在不修改类定义的情况下,为属性添加额外的行为。 属性委托的声明语法是 val/var
: by ,其中 by 后面的表达式是属性的委托对象。 日常开发中常用的委托实现是 by lazy ,实现对象的懒加载。
在 Jetpacka Compose 中,声明 State 类型的变量是会使用 by remember ,如果不使用的话会发生什么呢? remember 和属性委托又有什么关系呢?下面来探究一下。
Kotlin 属性委托
在面向对象编程的时候,所谓属性就是类的成员变量。对于成员变量唯一操作无非就是读写,对应的就是 get/set 方法。日常开发中,对于有些属性的 get/set 方法有特殊的要求,比如 set 方法要做类型、范围的检测,get 方法需要做返回值的变换之类的。在传统实现中,只能基于成员变量逐个修改,而 Kotlin 委托属性提供了更加灵活的语法。
下面举个栗子
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.")
}
}
class People {
var name by Delegate()
}
fun main() {
val people = People()
println(people.name)
people.name = "mike"
println(people.name)
}
com.engineer.compose.uitls.People@5dfcfece, thank you for delegating 'name' to me!
mike has been assigned to 'name' in com.engineer.compose.uitls.People@5dfcfece.
com.engineer.compose.uitls.People@5dfcfece, thank you for delegating 'name' to me!
比如在这个例子中,我们创建了一个类,自定义了 get/set 方法,而其中的关于值类型的参数是 String,这就相当于是将 String 这种类型变量的 get/set 方法统一委托到了 Delegate 这个类中。这样,我们需要用到使用 String 类型的成员变量时,就可以将他委托给一个 Delegate 对象。这样做的好处是,在委托实现中可以做处理通用逻辑,避免模板代码。
lazy 的原理
日常开发中,我们可以通过如下方式使用 lazy 申明一个不可变类型的变量
val hello : String by lazy { "hello" }
kotlin 通过强大的语法糖,将很多设计模式直接内化到了语法层面,这里 lazy 的具体实现就是一个例子。
Lazy 是一个接口,系统提供了这个接口的默认实现。我们以 SynchronizedLazyImpl 为例。
private class SynchronizedLazyImpl<out T>(initializer: () -> T, lock: Any? = null) : Lazy, Serializable {
private var initializer: (() -> T)? = initializer
@Volatile private var _value: Any? = UNINITIALIZED_VALUE
// final field is required to enable safe publication of constructed instance
private val lock = lock ?: this
override val value: T
get() {
val _v1 = _value
if (_v1 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST")
return _v1 as T
}
return synchronized(lock) {
val _v2 = _value
if (_v2 !== UNINITIALIZED_VALUE) {
@Suppress("UNCHECKED_CAST") (_v2 as T)
} else {
val typedValue = initializer!!()
_value = typedValue
initializer = null
typedValue
}
}
}
override fun isInitialized(): Boolean = _value !== UNINITIALIZED_VALUE
}
再看 lazy 函数
public inline operator fun Lazy .getValue(thisRef: Any?, property: KProperty<*>): T = value
public actual fun lazy(initializer: () -> T): Lazy = SynchronizedLazyImpl(initializer)
这样,由于 Lazy 提供了 getValue 方法,因此可以用 by 关键字进行属性委托(只读变量),而 lazy 函数提供可以返回任意 Lazy 类型的实现。这样,就实现了对任意类型属性写操作的委托。
viewModel 的委托
日常开发中,我们非常熟悉的 ViewModel ,每一次都需要在合适位置通过
ViewModelProvider(this)[XXXViewModel::class.java]
的方式进行初始化,这就显得很繁琐。其实我们完全可以借助属性委托的方式,将这个 XXXViewModel 的 set 操作抽象出来,避免模板代码,以更简单的方式将 ViewModel 的声明和初始化合并在一起,借助官方提供的扩展函数可以像下面这样实现
private val viewModel: XXXViewModel by viewModels()
//or
private val viewModel by viewModels()
viewModels 委托实现
@MainThread
public inline fun <reified VM : ViewModel> ComponentActivity.viewModels(
noinline extrasProducer: (() -> CreationExtras)? = null,
noinline factoryProducer: (() -> Factory)? = null
): Lazy {
val factoryPromise = factoryProducer ?: {
defaultViewModelProviderFactory
}
return ViewModelLazy(
VM::class,
{ viewModelStore },
factoryPromise,
{ extrasProducer?.invoke() ?: this.defaultViewModelCreationExtras }
)
}
public class ViewModelLazy<VM : ViewModel> @JvmOverloads constructor(
private val viewModelClass: KClass,
private val storeProducer: () -> ViewModelStore,
private val factoryProducer: () -> ViewModelProvider.Factory,
private val extrasProducer: () -> CreationExtras = { CreationExtras.Empty }
) : Lazy {
private var cached: VM? = null
override val value: VM
get() {
val viewModel = cached
return if (viewModel == null) {
val store = storeProducer()
val factory = factoryProducer()
val extras = extrasProducer()
ViewModelProvider.create(store, factory, extras)
.get(viewModelClass)
.also { cached = it }
} else {
viewModel
}
}
override fun isInitialized(): Boolean = cached != null
}
可以看到,这里其实也是借助了 Lazy 接口。viewModels() 作为 ComponentActivity 的扩展函数,可以获取到创建 ViewModel 的所有条件,开发者只需要提供 XXXViewModel 这样一个具体的泛型即可。同时 viewModels() 也支持开发者传入合适的参数,修改默认的行为。
Jetpack Compose 的属性委托
在 Jetpack Compose 中,也时常会看到 by remember 的身影,那么这里的属性委托又是如何实现的呢?
在前文Jetpack Compose State 你用对了吗? 中,我们看到使用不同的方式声明 State 类型的变量会有很大的差异。
@Composable
fun DemoCard() {
var count by remember { mutableIntStateOf(0) }
var list by remember { mutableStateOf(ArrayList()) }
val list2 = remember { mutableStateListOf() }
val list3 = remember { mutableStateListOf("") }
var list4 = remember { mutableStateOf(ArrayList())}
Log.i(TAG,"count -> ${count::class}")
Log.i(TAG,"list -> ${list::class}")
Log.i(TAG,"list2 -> ${list2::class}")
Log.i(TAG,"list3 -> ${list3::class}")
Log.i(TAG,"list4 -> ${list4::class}")
}
12:39:31.356 4522-4522 ComposeView I count -> class kotlin.Int
12:39:31.366 4522-4522 ComposeView I list -> class java.util.ArrayList
12:39:31.366 4522-4522 ComposeView I list2 -> class androidx.compose.runtime.snapshots.SnapshotStateList
12:39:31.367 4522-4522 ComposeView I list3 -> class androidx.compose.runtime.snapshots.SnapshotStateList
12:39:31.367 4522-4522 ComposeView I list4 -> class androidx.compose.runtime.ParcelableSnapshotMutableState
这里可以看到 remember 关键字本身和属性委托并没有什么关系,这一点我们从其实现也能看出端倪
inline fun remember(crossinline calculation: @DisallowComposableCalls () -> T): T =
currentComposer.cache(false, calculation)
顾名思义,在 Jetpack Compose 他提供了对 State 类型变量缓存或者说记忆的功能 。
在 Jetpack Compose 中,其实有两个接口 State 和 StateObject
/**
* Interface implemented by all snapshot aware state objects. Used by this module to maintain the
* state records of a state object.
*/
@JvmDefaultWithCompatibility
interface StateObject { }
StateObject 提供了在 Jetpack Compose 中维护 State 状态的接口。
State 是对基础类型结构的封装,同时扩展出了可变的 MutableState 类型。各种 MutableState 类型接口的具体实现会基于 StateObjectImpl 进行扩展。这样,基于 State 接口扩展的基础数据类型也可以被 Jetpack Compose 底层感知到数据的变化。那么,State 这个接口存在的意义是什么呢?
其实,就是为了委托。可以看到只有 State 接口及其扩展接口提供了 setValue 和 getValue 方法。
@Suppress("NOTHING_TO_INLINE")
inline operator fun State .getValue(thisObj: Any?, property: KProperty<*>): T = value
@Suppress("NOTHING_TO_INLINE")
inline operator fun MutableState .setValue(thisObj: Any?, property: KProperty<*>, value: T) {
this.value = value
}
通过属性委托,我们可以直接获取数据的原始类型,这里我们再看一下上面不同声明方式的返回类型
12:39:31.356 4522-4522 ComposeView I count -> class kotlin.Int
12:39:31.366 4522-4522 ComposeView I list -> class java.util.ArrayList
12:39:31.366 4522-4522 ComposeView I list2 -> class androidx.compose.runtime.snapshots.SnapshotStateList
12:39:31.367 4522-4522 ComposeView I list3 -> class androidx.compose.runtime.snapshots.SnapshotStateList
12:39:31.367 4522-4522 ComposeView I list4 -> class androidx.compose.runtime.ParcelableSnapshotMutableState
可以看到通过属性委托的方式,可以直接返回数据泛型参数中声明的类型,这样在实际编码中可以更方便,这么说可能不太直观,我们再举个例子。
@Composable
fun Counter(modifier: Modifier = Modifier) {
val count = remember { mutableStateOf(0) }
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "${count.value}",
fontSize = 50.sp
)
Button(
onClick = { count.value++ }
) {
Text(
text = "Click me",
fontSize = 26.sp
)
}
}
}
@Composable
fun Counter(modifier: Modifier = Modifier) {
var count by remember { mutableStateOf(0) }
Column(
modifier = modifier,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "$count",
fontSize = 50.sp
)
Button(
onClick = { count++ }
) {
Text(
text = "Click me",
fontSize = 26.sp
)
}
}
}
以上两份代码实现的功能完全一致,但是通过使用 by 实现属性委托之后,代码的实现明显更简洁了。从 State 接口的继承结构可以看到,他只提供了基础数据类型的扩展,因此对于 List/Map 这种类型的数据,暂时是无法通过属性委托的方式进行声明的。
小结
属性委托其实就是代理模式,只不过在 Kotlin 中结合泛型和扩展函数、函数可以作为方法参数等语法糖的优势,通过简洁的语法实现类成员变量的更高效和灵活的读写,使得编码的实现更加简洁。
参考文档