@@ -18,16 +18,51 @@ package io.realm.kotlin.internal.platform
1818
1919import io.realm.kotlin.internal.RealmObjectCompanion
2020import io.realm.kotlin.types.BaseRealmObject
21+ import java.util.concurrent.ConcurrentHashMap
2122import kotlin.reflect.KClass
22- import kotlin.reflect.full.companionObjectInstance
2323
2424// TODO OPTIMIZE Can we eliminate the reflective approach? Maybe by embedding the information
2525// through the compiler plugin or something similar to the Native findAssociatedObject
2626@PublishedApi
27- internal actual fun <T : Any > realmObjectCompanionOrNull (clazz : KClass <T >): RealmObjectCompanion ? =
28- if (clazz.companionObjectInstance is RealmObjectCompanion ) {
29- clazz.companionObjectInstance as RealmObjectCompanion
30- } else null
27+ internal actual fun <T : Any > realmObjectCompanionOrNull (clazz : KClass <T >): RealmObjectCompanion ? {
28+ // The implementation of this method was changed for performance reasons as it was using
29+ // kotlin.reflect.full before, and getting companion objects this way on Android is very slow.
30+ // See this PR: https://github.com/realm/realm-kotlin/pull/1851
31+ //
32+ // 1. We optimized this function for the case where it'd be called with `clazz` being a
33+ // primitive type (boxed or not)
34+ //
35+ // 2. We cache the value even if there's no companion object,to avoid calling `Class.forName(…)`
36+ // too often, because it's still slow reflection (though less slow than what we're replacing).
37+ //
38+ // 3. used `ConcurrentHashMap` because we didn't check this function can never be called
39+ // concurrently, and if it was, the consequences would be tough, surprising, and hard to
40+ // diagnose, granted it was a basic (and NOT thread-safe `HashMap`).
41+ // It's not just about doing the lookup twice, but about infinite loops while iterating, i.e.,
42+ // tough consequences.
43+
44+ val cachedClass = reflectionCache[clazz]
45+ if (cachedClass != null ) return cachedClass as ? RealmObjectCompanion
46+
47+ val newValue: Any = clazz.javaPrimitiveType ? : (try {
48+ Class .forName(" ${clazz.java.name} \$ Companion" ).kotlin
49+ } catch (_: ClassNotFoundException ) {
50+ try {
51+ // For Parcelable classes
52+ Class .forName(" ${clazz.java.name} \$ CREATOR" ).kotlin
53+ } catch (_: ClassNotFoundException ) {
54+ null
55+ }
56+ }?.objectInstance as ? RealmObjectCompanion ) ? : clazz
57+
58+ reflectionCache[clazz] = newValue
59+
60+ return newValue as ? RealmObjectCompanion
61+ }
62+
63+ // Since ConcurrentHashMap doesn't accept null values, we can't use `RealmObjectCompanion?`,
64+ // so we use `Any`, and we actually put a class, or a `RealmObjectCompanion` instance inside.
65+ private val reflectionCache = ConcurrentHashMap <KClass <* >, Any > ()
3166
3267@PublishedApi
3368internal actual fun <T : BaseRealmObject > realmObjectCompanionOrThrow (clazz : KClass <T >): RealmObjectCompanion =
0 commit comments