What would be the best way to create an implementa...
# kotlin-native
t
What would be the best way to create an implementation for suspend functions in Swift/ObjectiveC? Suspend functions are not visible in the exported framework, this is because ObjectiveC does not have “suspend functions” (https://github.com/JetBrains/kotlin-native/issues/1684). I use suspend functions for showing a dialog (a dialog can return a result or be cancelled). For example the following View interface:
Copy code
interface View : MvpView {
    // shows a dialog which returns a String.
    suspend fun showDialog(): String
}
I’m not sure how I can implement this for iOS. I thought the following workaround would work:
Copy code
interface View : MvpView {
    // implement this in Android (Kotlin)
    suspend fun showDialog(): String = suspendCancellableCoroutine { showDialog(it) }

    // implement this in iOS (Swift)
    fun showDialog(cont: CancellableContinuation<String>): Unit = throw NotImplementedError()
}
This would allow me to use suspend functions from the calling code and the Android implementation, and just a normal function for iOS. However, it looks like default functions for interfaces are not supported. (https://github.com/JetBrains/kotlin-native/issues/2638) What would be the best way to solve this? Not use suspend functions at all?
t
tldr: not using suspend functions from swift code. Either you write your ViewController in kotlin or you keep the interface synchronous and handle the suspending code in the presenter.
s
As a workaround, you can turn this function
Copy code
suspend fun showDialog(): String = suspendCancellableCoroutine { showDialog(it) }
into extension.
t
@svyatoslav.scherbina if I define it as an extension function, I'm not sure how I would implement that in Android. Or is this so I don't need to put this function code in the presenter, and only use the normal function for the implementations?
s
You can apply the following approach:
Copy code
interface View
interface IosView : View {
    fun showDialog(cont: CancellableContinuation<String>): Unit
}
interface AndroidView : View {
    suspend fun showDialog(): String
}

suspend fun View.showDialog(): String = when (this) {
    is AndroidView -> this.suspendDialog()
    is IosView -> suspendCancellableCoroutine { showDialog(it) }
    else -> TODO()
}
c
I tried something as bellow. In the common module I have:
Copy code
interface View{
  suspend fun myFunSuspend().
}

// for Android, normal implementation of co routine
// for iOS see below

class iOSViewWrapper(private val iOSImpl: IosView) : View {

  override suspend fun myFunSuspend(){
    return suspendCoroutine {
      iOSImpl.doAsyncJob{ data, error ->
        if(error != null){
          it.resumeWithException(error)
        }else{
          it.resume(data)
        }
      }
    } 
  }
}

interface IosView {
  fun doAsyncJob((data: ByteArray, error: Throwable?)-> Unit)
}
the function
doAsyncJob
implementation is done in Swift and is called many times inside my kotlin code. I have defined a coroutine scope that is using the ios
dispatch_get_main_queue
. I have made sure that
doAsyncJob
is execute AND invoke the closure on the main thread. The code works but crashes always in the swift invocation of the closure. Sometimes after 1 call sometimes after 6 or 10, ... I tried to declare the closure of
doAsyncJob
as a variable and call
.freeze()
on it, but I get
Copy code
kotlinx.coroutines.CoroutinesInternalError: Fatal exception in coroutines machinery for DispatchedContinuation[MainDispatcher@7d793018, Continuation @ $prepareCOROUTINE$11]. Please read KDoc to 'handleFatalException' method and report this incident to maintainers
        at 0   mylib                            0x0000000100f81e98 kfun:kotlin.Error.<init>(kotlin.String?;kotlin.Throwable?)kotlin.Error + 24
        at 1   mylib                            0x0000000100f81bc0 kfun:kotlinx.coroutines.CoroutinesInternalError.<init>(kotlin.String;kotlin.Throwable)kotlinx.coroutines.CoroutinesInternalError + 24
        at 2   mylib                            0x0000000100f81818 kfun:kotlinx.coroutines.DispatchedTask.handleFatalException$kotlinx-coroutines-core(kotlin.Throwable?;kotlin.Throwable?) + 284
        at 3   mylib                            0x0000000100f82540 kfun:kotlinx.coroutines.DispatchedTask.run() + 1224
what am I doing wrong?
I am using kotlin
1.3.41
and coroutine
1.3.0-M2
with an mpp project android and ios @svyatoslav.scherbina
s
The code works but crashes always in the swift invocation of the closure.
Could you share the stack trace of such a crash?
I tried to declare the closure of
doAsyncJob
as a variable and call
.freeze()
This transitively freezes your continuation too (since it is captured), which is not correct.
c
Copy code
#0	0x000000010276db6c in UpdateHeapRef ()
#1	0x00000001026be4cc in blockDisposeHelper ()
#2	0x00000001af1dca48 in _Block_release ()
#3	0x00000001021e10c4 in ___lldb_unnamed_symbol13$$iosSampleApp ()
#4	0x00000001bcdfe9ac in _swift_release_dealloc ()
#5	0x00000001021e0e48 in ___lldb_unnamed_symbol12$$iosSampleApp ()
#6	0x00000001bcdfe9ac in _swift_release_dealloc ()
#7	0x00000001021c6280 in ___lldb_unnamed_symbol7$$iosSampleApp ()
#8	0x00000001bcdfe9ac in _swift_release_dealloc ()
#9	0x00000001021c6408 in block_destroy_helper ()
#10	0x00000001af1dca48 in _Block_release ()
#11	0x00000001af1dca48 in _Block_release ()
#12	0x00000001d8091cf0 in __destroy_helper_block_e8_32o40o48o56b ()
#13	0x00000001af1dca48 in _Block_release ()
#14	0x0000000102a46c88 in _dispatch_client_callout ()
#15	0x0000000102a4dbf8 in _dispatch_lane_serial_drain ()
#16	0x0000000102a4e7c8 in _dispatch_lane_invoke ()
#17	0x0000000102a59b88 in _dispatch_workloop_worker_thread ()
#18	0x00000001af304fa8 in _pthread_wqthread ()
Enqueued from com.apple.main-thread (Thread 1) Queue : com.apple.main-thread (serial)
#0	0x0000000102a4b0dc in dispatch_async ()
#1	0x00000001d8091b30 in -[Tag _sendWithData:completionHandler:] ()
#2	0x00000001d8091ea4 in -[Tag _send:completionHandler:] ()
#3	0x00000001021c60dc in Session.sendCommand(command:completionHandler:) at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/iosSampleApp/iosSampleApp/iosSampleApp/ReaderSession.swift:92
#4	0x00000001021e0410 in Transceiver.sendCommand(dataToSend:completionHandler:) at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/iosSampleApp/iosSampleApp/iosSampleApp/iOSController.swift:46
#5	0x00000001021e1034 in @objc Transceiver.sendCommand(dataToSend:completionHandler:) ()
#6	0x00000001027485dc in ___lldb_unnamed_symbol355$$myapp ()
#7	0x00000001027214fc in kfun:com.myApp.session.MessagingSession.$sendCommandCOROUTINE$12.invokeSuspend(kotlin.Result<kotlin.Any?>)kotlin.Any? at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/commonMain/kotlin/com/my/app/session/MessagingSession.kt:53
#8	0x000000010271fdc8 in kfun:com.myApp.session.MessagingSession.sendCommand(com.myApp.Command)com.myApp.Response at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/commonMain/kotlin/com/my/app/session/MessagingSession.kt:38
#9	0x0000000102722708 in kfun:com.myApp.reader.FileReader.$readFirstBlockCOROUTINE$7.invokeSuspend#internal at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/commonMain/kotlin/com/my/app/reader/FileReader.kt:106
Copy code
#10	0x000000010271ebf4 in kfun:com.myApp.reader.FileReader.readFirstBlock#internal at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/commonMain/kotlin/com/my/app/reader/FileReader.kt:99
#11	0x000000010271e76c in kfun:com.myApp.reader.FileReader.$readCOROUTINE$5.invokeSuspend(kotlin.Result<kotlin.Any?>)kotlin.Any? at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/commonMain/kotlin/com/my/app/reader/FileReader.kt:51
#12	0x000000010271e5dc in kfun:com.myApp.reader.FileReader.read()kotlin.ByteArray at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/commonMain/kotlin/com/my/app/reader/FileReader.kt:48
#13	0x000000010271d6dc in kfun:com.myApp.reader.ReaderWorker.read(com.myApp.chipinfo.ChipInfo;com.myApp.session.MessagingSession?;com.myApp.reader.FileType;kotlin.Function2<kotlin.Int,<http://kotlin.Int|kotlin.Int>,kotlin.Unit>?)kotlin.ByteArray at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/commonMain/kotlin/com/my/app/reader/ReaderWorker.kt:120
#14	0x000000010271ca54 in <inlined-out:<anonymous>> [inlined] at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/commonMain/kotlin/com/my/app/Reader.kt:84
#15	0x000000010271c880 in kfun:kotlin.collections.forEach@kotlin.collections.Iterable<#GENERIC>.(kotlin.Function1<#GENERIC,kotlin.Unit>)Generic [inlined] at /Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/generated/_Collections.kt:146
#16	0x000000010271c880 in kfun:com.myApp.Reader.$read$lambda-0COROUTINE$0.invokeSuspend#internal at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/commonMain/kotlin/com/my/app/Reader.kt:79
#17	0x000000010270568c in <inlined-out:<anonymous>> [inlined] at /Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:30
#18	0x0000000102705650 in kfun:kotlin.with(#GENERIC;kotlin.Function1<#GENERIC,#GENERIC>)Generic [inlined] at /Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/util/Standard.kt:53
#19	0x0000000102705650 in kfun:kotlin.coroutines.native.internal.BaseContinuationImpl.resumeWith(kotlin.Result<kotlin.Any?>) at /Users/teamcity/buildAgent/work/4d622a065c544371/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:26
#20	0x0000000102709920 in kfun:kotlin.coroutines.resume@kotlin.coroutines.Continuation<#GENERIC>.(#GENERIC)Generic [inlined] at /Users/teamcity/buildAgent/work/4d622a065c544371/backend.native/build/stdlib/kotlin/coroutines/Continuation.kt:44
#21	0x00000001027098f0 in <inlined-out:<anonymous>> [inlined] at /opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/Dispatched.kt:158
#22	0x00000001027098f0 in kfun:kotlinx.coroutines.withCoroutineContext$kotlinx-coroutines-core(kotlin.coroutines.CoroutineContext;kotlin.Any?;kotlin.Function0<#GENERIC>)Generic [inlined] at /opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/CoroutineContext.kt:47
#23	0x00000001027098f0 in kfun:kotlinx.coroutines.DispatchedTask.run() at /opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/Dispatched.kt:158
#24	0x000000010274a5a0 in kfun:com.myApp.util.MainDispatcher.dispatch$lambda-0#internal at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/iosMain/kotlin/com/my/app/util/Coroutines.kt:24
#25	0x000000010274a550 in kfun:com.myApp.util.MainDispatcher.$dispatch$lambda-0$FUNCTION_REFERENCE$46.invoke#internal at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/iosMain/kotlin/com/my/app/util/Coroutines.kt:23
#26	0x000000010274a51c in kfun:com.myApp.util.MainDispatcher.$dispatch$lambda-0$FUNCTION_REFERENCE$46.$<bridge-UNN>invoke()#internal at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/mpp/src/iosMain/kotlin/com/my/app/util/Coroutines.kt:23
Copy code
#27	0x000000010276846c in platform_darwin_kniBridge202 at /Users/teamcity/buildAgent/work/4d622a065c544371/platformLibs/build/konan/libs/ios_arm64/darwin.klib-build/kotlin/platform/darwin/darwin.kt:24587
#28	0x000000010277dce4 in __platform_darwin_kniBridge201_block_invoke ()
#29	0x0000000102a458ac in _dispatch_call_block_and_release ()
#30	0x0000000102a46c88 in _dispatch_client_callout ()
#31	0x0000000102a54ce8 in _dispatch_main_queue_callback_4CF ()
#32	0x00000001af5648bc in __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ ()
#33	0x00000001af55f8b0 in __CFRunLoopRun ()
#34	0x00000001af55edb4 in CFRunLoopRunSpecific ()
#35	0x00000001b986e328 in GSEventRunModal ()
#36	0x00000001b35b5e94 in UIApplicationMain ()
#37	0x00000001021bcfb4 in main at /Users/cyrille.quemin/Documents/git/mpp-trial-coroutine/iosSampleApp/iosSampleApp/AppDelegate.swift:12
#38	0x00000001af3ea424 in start ()
this is the full stack trace which is triggered from a bacground thread from iOS, before the call back is invoked on the main thread. When @svyatoslav.scherbina said
This transitively freezes your continuation too (since it is captured), which is not correct.
I am not sure what to do since it feels like the closure is freed from memory if not held in a member vsriable in the swift class. Which is what is causing the crash: The closure cannot be invoked
s
The original crash is caused by a known issue. We are working on the fix.
I am not sure what to do since it feels like the closure is freed from memory if not held in a member vsriable in the swift class. Which is what is causing the crash: The closure cannot be invoked
No.
c
Ah ok cool, thanks for letting me know. By any chance do you have a tracking ticket?
s