tk
01/13/2025, 8:00 AMobject KoinDependencyResolver : KoinComponent {
@OptIn(BetaInteropApi::class)
public fun resolve(
objCObject: ObjCObject,
qualifier: Qualifier? = null,
parameters: List<Any?> = emptyList(),
): Any {
val clazz = when (objCObject) {
is ObjCClass -> getOriginalKotlinClass(objCClass = objCObject)
is ObjCProtocol -> getOriginalKotlinClass(objCProtocol = objCObject)
else -> null
} ?: error("Could not find any corresponding Kotlin classes of $objCObject.")
return getKoin().get(
clazz = clazz,
qualifier = qualifier,
parameters = { parametersOf(parameters) }.takeIf { parameters.isNotEmpty() },
)
}
}
Then made the following plain object for injection.
// a sample class to be injected
data class SampleData(val name: String, val age: Int)
// call it in the iOSApp entrypoint through a wrapper function like KoinIOS.startKoin()
val appModule = module {
...
factory { (name: String, age: Int) ->
SampleData(name, age)
}
}
[iosApp]
Trying to get the injected instance in ContentView with the following function.
func get<T: AnyObject>(qualifier: Koin_coreQualifier? = nil, parameters: [Any] = []) -> T {
return KoinDependencyResolver.shared.resolve(objCObject: T.self, qualifier: qualifier, parameters: parameters) as! T
}
// in ContentView, both didn't work
private let sampleData: SampleData = get(parameters: ["Smith", 30])
private let sampleData: SampleData = get(parameters: ["Smith", 30 as NSNumber])
The error I got on the injection.
🔴 (koin) * Instance creation error : could not create instance for '[Factory: 'my.app.sample.SampleData']': org.koin.core.error.NoParameterFoundException: Can't get injected parameter #1 from DefinitionParameters[[Smith, 30]] for type '<http://kotlin.Int|kotlin.Int>'[kotlin.Exception]
Supplement: The following instance injection with only String param succeeded.
// in shared/commonMain
data class SampleData(val name: String)
// in iosApp
private let sampleData: SampleData = get(parameters: ["Smith"])
tk
01/13/2025, 8:23 AMprivate let sampleData: SampleData = get(parameters: ["Smith", KotlinInt(int: 30)])
arnaud.giuliani
01/16/2025, 12:16 PMresolve
functionarnaud.giuliani
01/16/2025, 12:16 PMtk
01/16/2025, 3:26 PM// a sample class to be injected
data class SampleData(val firstName: String, val lastName: String)
// call it in the iOSApp entrypoint through a wrapper function like KoinIOS.startKoin()
val appModule = module {
...
factory { (firstName: String, lastName: String) ->
SampleData(firstName, lastName)
}
}
And passing 2 string data from Swift code also fails.
At first, I thought the main cause came from the limitation of Kotlin / Objective-C interoperability on primitive types, but there might be some other reasons.
// in ContentView, it didn't work
private let sampleData: SampleData = get(parameters: ["John", "Smith"])
Ref: 🪵
iosApp[84960:9029428] :red_circle: (koin) * Instance creation error : could not create instance for '[Factory: 'my.app.sample.SampleData']': org.koin.core.error.NoParameterFoundException: Can't get injected parameter #1 from DefinitionParameters[[John, Smith]] for type 'kotlin.String'[kotlin.Exception]
Function doesn't have or inherit @Throws annotation and thus exception isn't propagated from Kotlin to Objective-C/Swift as NSError.
It is considered unexpected and unhandled instead. Program will be terminated.
org.koin.core.error.InstanceCreationException: Could not create instance for '[Factory: 'my.app.sample.SampleData']'
Uncaught Kotlin exception: at 0 shared 0x1062e73cf kfun:kotlin.Exception#<init>(kotlin.String?;kotlin.Throwable?){} + 123 (/opt/buildAgent/work/b5c630f73501b353/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:25:70)
at 1 shared 0x1064c6e5f kfun:org.koin.core.error.InstanceCreationException#<init>(kotlin.String;kotlin.Exception){} + 123 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/error/InstanceCreationException.kt:23:67)
at 2 shared 0x1064c8217 kfun:org.koin.core.instance.InstanceFactory#create(org.koin.core.instance.ResolutionContext){}1:0 + 1703 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/instance/InstanceFactory.kt:58:19)
at 3 shared 0x1064c7a47 kfun:org.koin.core.instance.FactoryInstanceFactory#get(org.koin.core.instance.ResolutionContext){}1:0 + 103 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/instance/FactoryInstanceFactory.kt:38:16)
at 4 shared 0x1064e1b03 kfun:org.koin.core.instance.InstanceFactory#get(org.koin.core.instance.ResolutionContext){}1:0-trampoline + 67 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/instance/InstanceFactory.kt:40:5)
at 5 shared 0x1064d2daf kfun:org.koin.core.registry.InstanceRegistry#resolveInstance(org.koin.core.qualifier.Qualifier?;kotlin.reflect.KClass<*>;org.koin.core.qualifier.Qualifier;org.koin.core.instance.ResolutionContext){0§<kotlin.Any?>}0:0? + 343 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/registry/InstanceRegistry.kt:110:69)
at 6 shared 0x1064daf4b kfun:org.koin.core.scope.Scope.resolveFromRegistry#internal + 323 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:321:39)
at 7 shared 0x1064da3ab kfun:org.koin.core.scope.Scope.resolveFromContext#internal + 1503 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:311:16)
at 8 shared 0x1064d9713 kfun:org.koin.core.scope.Scope.stackParametersCall#internal + 871 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:281:20)
at 9 shared 0x1064d9363 kfun:org.koin.core.scope.Scope.resolveInstance#internal + 867 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:259:16)
at 10 shared 0x1064d8857 kfun:org.koin.core.scope.Scope.resolveWithOptionalLogging#internal + 1019 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:232:20)
at 11 shared 0x1064d833f kfun:org.koin.core.scope.Scope#get(kotlin.reflect.KClass<*>;org.koin.core.qualifier.Qualifier?;kotlin.Function0<org.koin.core.parameter.ParametersHolder>?){0§<kotlin.Any?>}0:0 + 323 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/scope/Scope.kt:215:16)
at 12 shared 0x1064c148b kfun:org.koin.core.Koin#get(kotlin.reflect.KClass<*>;org.koin.core.qualifier.Qualifier?;kotlin.Function0<org.koin.core.parameter.ParametersHolder>?){0§<kotlin.Any?>}0:0 + 259 (/Users/runner/work/koin/koin/projects/core/koin-core/src/commonMain/kotlin/org/koin/core/Koin.kt:138:36)
...
arnaud.giuliani
01/16/2025, 5:33 PMtk
01/17/2025, 5:10 PM// a sample class to be injected
data class SampleData(val properties: Properties) {
data class Properties(val firstName: String, val lastName: String, val age: Int)
}
// call it in the iOSApp entrypoint through a wrapper function like KoinIOS.startKoin()
val appModule = module {
...
factory { (properties: Properties) ->
SampleData(properties)
}
}
// in ContentView, no errors
private let sampleData: SampleData = get(parameters: SampleData.Properties(firstName: "John", lastName: "Smith", age: 30))
However, I got the following exception if I tried to get any property (either firstName, lastName or age) in Swift.
Seems like the exception might be related to the interoperability among languages though 🤔
// throws an exception
print(sampleData.properties.firstName)
Ref: 🪵
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Swift.__SwiftDeferredNSArray firstName]: unrecognized selector sent to instance 0x600000215200'
*** First throw call stack:
(
0 CoreFoundation 0x00000001804b910c __exceptionPreprocess + 172
1 libobjc.A.dylib 0x0000000180092da8 objc_exception_throw + 72
2 CoreFoundation 0x00000001804cebf4 +[NSObject(NSObject) instanceMethodSignatureForSelector:] + 0
3 CoreFoundation 0x00000001804bd40c ___forwarding___ + 1268
4 CoreFoundation 0x00000001804bfb2c _CF_forwarding_prep_0 + 92
5 iosApp.debug.dylib 0x0000000102f6361c $s6iosApp11ContentViewV8loadDatayyF + 88
6 iosApp.debug.dylib 0x0000000102f634b8 $s6iosApp11ContentViewV4bodyQrvgyycfU_ + 28
7 SwiftUICore 0x00000001d3ef65f8 $sIeg_ytIegr_TR + 20
8 SwiftUICore 0x00000001d3ef65
tk
01/19/2025, 12:03 AMlet sampleData1: SampleData = get(parameters: SampleData.Properties(firstName: "John", lastName: "Smith", age: 30))
let sampleData2 = SampleData(properties: SampleData.Properties(firstName: "John", lastName: "Doe", age: 30))
print(type(of: sampleData1)) // prints SharedSampleData
print(type(of: sampleData2)) // prints SharedSampleData
print(type(of: sampleData1.properties)) // prints __SwiftDeferredNSArray 😮
print(type(of: sampleData2.properties)) // prints SharedSampleDataProperties
print(sampleData1.firstName) // crashes
print(sampleData2.firstName) // succeeds
Moreover, if I prepare a dedicated class that extends KoinComponent
as suggested in the official doc, it also worked as expected.
public object SampleDataComponent : KoinComponent {
public fun provide(properties: SampleData.Properties): SampleData {
val sampleData: SampleData by inject { parametersOf(properties) }
return sampleData
}
}
Then I noticed the received `parameters`'s type is kotlin.native.internal.NSArrayAsKList
in my KoinDependencyResolver
, which caused the crash above 🤦♂️tk
01/19/2025, 3:29 AMparameter
in my KoinDependencyResolver
!
As-Is
parameters = { parametersOf(parameters) }.takeIf { parameters.isNotEmpty() },
To-Be
parameters = { parametersOf(*parameters.toTypedArray()) }.takeIf { parameters.isNotEmpty() },