• 作者:老汪软件技巧
  • 发表时间: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.png

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

stateObj.png

/**
 * 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.png

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 中结合泛型和扩展函数、函数可以作为方法参数等语法糖的优势,通过简洁的语法实现类成员变量的更高效和灵活的读写,使得编码的实现更加简洁。

参考文档