@@ -18,16 +18,53 @@ 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+ if (clazz.javaPrimitiveType != null ) error(" Ahem" )
48+
49+ val newValue: Any = clazz.javaPrimitiveType ? : (try {
50+ Class .forName(" ${clazz.java.name} \$ Companion" ).kotlin
51+ } catch (_: ClassNotFoundException ) {
52+ try {
53+ // For Parcelable classes
54+ Class .forName(" ${clazz.java.name} \$ CREATOR" ).kotlin
55+ } catch (_: ClassNotFoundException ) {
56+ null
57+ }
58+ }?.objectInstance as ? RealmObjectCompanion ) ? : clazz
59+
60+ reflectionCache[clazz] = newValue
61+
62+ return newValue as ? RealmObjectCompanion
63+ }
64+
65+ // Since ConcurrentHashMap doesn't accept null values, we can't use `RealmObjectCompanion?`,
66+ // so we use `Any`, and we actually put a class, or a `RealmObjectCompanion` instance inside.
67+ private val reflectionCache = ConcurrentHashMap <KClass <* >, Any > ()
3168
3269@PublishedApi
3370internal actual fun <T : BaseRealmObject > realmObjectCompanionOrThrow (clazz : KClass <T >): RealmObjectCompanion =
0 commit comments