Котлин ленивые свойства и ценности reset: сбрасываемый ленивый делегат

Поэтому я использую kotlin для android, и при раздувании представлений я стараюсь сделать следующее:

private val recyclerView by lazy { find<RecyclerView>(R.id.recyclerView) }

Этот метод будет работать. Тем не менее, есть случай, в котором это приведет к ошибке приложения. Если это фрагмент, и фрагмент идет в заднюю часть, onCreateView будет вызван снова, и иерархия представления фрагмента будет воссоздана. Это означает, что ленивый инициированный recyclerView укажет на старый вид, который больше не существует.

Решение таково:

private lateinit var recyclerView: RecyclerView

И инициализируйте все свойства внутри onCreateView.

Мой вопрос в том, есть ли способ для reset ленивых свойств, чтобы их можно было снова инициализировать? Мне нравится, что все инициализации выполняются в верхней части класса, помогает сохранить код организованным. Конкретная проблема находится в этом вопросе: kotlin андроид фрагмент пустой просмотр ресайлера после назад

Ответы

Ответ 1

Вот краткая версия сбрасываемого лени, она может быть более элегантной и требует двойной проверки безопасности потоков, но это в основном идея. Вам нужно что-то контролировать (отслеживать) ленивых делегатов, чтобы вы могли вызвать reset, а затем все, что можно управлять, и reset. Это обертывает lazy() в этих классах управления.

Вот как выглядит ваш последний класс:

class Something {
    val lazyMgr = resettableManager()
    val prop1: String by resettableLazy(lazyMgr) { ... }
    val prop2: String by resettableLazy(lazyMgr) { ... }
    val prop3: String by resettableLazy(lazyMgr) { ... }
}

Затем, чтобы ленивые все вернулись к новым значениям в следующий раз, к ним обращаются:

lazyMgr.reset() // prop1, prop2, and prop3 all will do new lazy values on next access

Реализация сбрасываемого лени:

class ResettableLazyManager {
    // we synchronize to make sure the timing of a reset() call and new inits do not collide
    val managedDelegates = LinkedList<Resettable>()

    fun register(managed: Resettable) {
        synchronized (managedDelegates) {
            managedDelegates.add(managed)
        }
    }

    fun reset() {
        synchronized (managedDelegates) {
            managedDelegates.forEach { it.reset() }
            managedDelegates.clear()
        }
    }
}

interface Resettable {
    fun reset()
}

class ResettableLazy<PROPTYPE>(val manager: ResettableLazyManager, val init: ()->PROPTYPE): Resettable {
    @Volatile var lazyHolder = makeInitBlock()

    operator fun getValue(thisRef: Any?, property: KProperty<*>): PROPTYPE {
        return lazyHolder.value
    }

    override fun reset() {
        lazyHolder = makeInitBlock()
    }

    fun makeInitBlock(): Lazy<PROPTYPE> {
        return lazy {
            manager.register(this)
            init()
        }
    }
}

fun <PROPTYPE> resettableLazy(manager: ResettableLazyManager, init: ()->PROPTYPE): ResettableLazy<PROPTYPE> {
    return ResettableLazy(manager, init)
}

fun resettableManager(): ResettableLazyManager = ResettableLazyManager()

И некоторые модульные тесты:

class Tester {
   @Test fun testResetableLazy() {
       class Something {
           var seed = 1
           val lazyMgr = resettableManager()
           val x: String by resettableLazy(lazyMgr) { "x ${seed}" }
           val y: String by resettableLazy(lazyMgr) { "y ${seed}" }
           val z: String by resettableLazy(lazyMgr) { "z $x $y"}
       }

       val s = Something()
       val x1 = s.x
       val y1 = s.y
       val z1 = s.z

       assertEquals(x1, s.x)
       assertEquals(y1, s.y)
       assertEquals(z1, s.z)

       s.seed++ // without reset nothing should change

       assertTrue(x1 === s.x)
       assertTrue(y1 === s.y)
       assertTrue(z1 === s.z)

       s.lazyMgr.reset()

       s.seed++ // because of reset the values should change

       val x2 = s.x
       val y2 = s.y
       val z2 = s.z

       assertEquals(x2, s.x)
       assertEquals(y2, s.y)
       assertEquals(z2, s.z)

       assertNotEquals(x1, x2)
       assertNotEquals(y1, y2)
       assertNotEquals(z1, z2)

       s.seed++ // but without reset, nothing should change

       assertTrue(x2 === s.x)
       assertTrue(y2 === s.y)
       assertTrue(z2 === s.z)
   }
}

Ответ 2

У меня была одна и та же задача, и вот что я использовал:

import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty

class SingletonLazy<T : Any>(val initBlock: () -> T, val clazz: Class<T>) {
    operator fun <R> provideDelegate(ref: R, prop: KProperty<*>): ReadOnlyProperty<R, T> = delegate()

    @Suppress("UNCHECKED_CAST")
    private fun <R> delegate(): ReadOnlyProperty<R, T> = object : ReadOnlyProperty<R, T> {
        override fun getValue(thisRef: R, property: KProperty<*>): T {
            val hash = clazz.hashCode()
            val cached = singletonsCache[hash]
            if (cached != null && cached.javaClass == clazz) return cached as T
            return initBlock().apply { singletonsCache[hash] = this }
        }
    }
}

private val singletonsCache = HashMap<Int, Any>()

fun <T> clearSingleton(clazz: Class<T>) : Boolean {
    val hash = clazz.hashCode()
    val result = singletonsCache[hash]
    if (result?.javaClass != clazz) return false

    singletonsCache.remove(hash)
    return true
}

inline fun <reified T : Any> singletonLazy(noinline block: () -> T): SingletonLazy<T>
        = SingletonLazy(block, T::class.java)

использование:

val cat: Cat by singletonLazy { Cat() }

fun main(args: Array<String>) {
    cat
    println(clearSingleton(Cat::class.java))
    cat // cat will be created one more time
    println(singletonsCache.size)
}

class Cat {
    init { println("creating cat") }
}

Конечно, у вас могут быть собственные стратегии кэширования.