Skip to content

Commit 3b610b3

Browse files
committed
Workaround reflection-related performance issue
This should fix realm#1544 See this PR that was the inspiration: realm#1851
1 parent 78d8d35 commit 3b610b3

File tree

1 file changed

+40
-5
lines changed
  • packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform

1 file changed

+40
-5
lines changed

packages/library-base/src/jvm/kotlin/io/realm/kotlin/internal/platform/RealmObject.kt

Lines changed: 40 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,51 @@ package io.realm.kotlin.internal.platform
1818

1919
import io.realm.kotlin.internal.RealmObjectCompanion
2020
import io.realm.kotlin.types.BaseRealmObject
21+
import java.util.concurrent.ConcurrentHashMap
2122
import 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
3368
internal actual fun <T : BaseRealmObject> realmObjectCompanionOrThrow(clazz: KClass<T>): RealmObjectCompanion =

0 commit comments

Comments
 (0)