Skip to content

Commit ce57ab6

Browse files
committed
Remove kotlin reflect & workaround performance issue
This should fix realm#1544 See this PR that was the inspiration: realm#1851
1 parent 96dce9e commit ce57ab6

File tree

2 files changed

+42
-6
lines changed

2 files changed

+42
-6
lines changed

packages/library-base/build.gradle.kts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ kotlin {
5252
val commonMain by getting {
5353
dependencies {
5454
implementation(kotlin("stdlib-common"))
55-
implementation(kotlin("reflect"))
5655
// If runtimeapi is merged with cinterop then we will be exposing both to the users
5756
// Runtime holds annotations, etc. that has to be exposed to users
5857
// Cinterop does not hold anything required by users

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

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -18,16 +18,53 @@ 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+
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
3370
internal actual fun <T : BaseRealmObject> realmObjectCompanionOrThrow(clazz: KClass<T>): RealmObjectCompanion =

0 commit comments

Comments
 (0)