Kotlin coroutines: Dispatchers.Main context for RoboVM applications
07 May 2021 | kotlin coroutinesAndroid has kotlinx-coroutines-android Dispatcher.Main
that coroutine execution on main/ui
thread.
It allows writing an effective suspend logic on main thread as described on the usage page:
fun setup(hello: Text, fab: Circle) {
GlobalScope.launch(Dispatchers.Main) { // launch coroutine in the main thread
for (i in 10 downTo 1) { // countdown from 10 to 1
hello.text = "Countdown $i ..." // update text
delay(500) // wait half a second
}
hello.text = "Done!"
}
}
Overview
To make Dispatchers.Main
available to kotlinx
following steps to be done:
- MainDispatcherFactory implementation to be registered through META-INF/services;
Factory
should produce MainCoroutineDispatcher implementation;MainCoroutineDispatcher
is responsible for dispatchingrunnables
on main/ui context.
Approaches
Implementation is a port of kotlinx-coroutines-android where dispatch was happening using android.os.Handler
.
For RoboVM
there were several options:
- using Grand Dispatch Central;
- using Operation Queues;
- using NSObject performSelectorOnMainThread:::;
- using NSRunLoop.
To implement dispatch following was items should be possible in implementation:
- cancellation of pending execution;
- ability to determine if code currently executing inside context;
These are possible with Grand Dispatch Central
and Operation Queues
. Last are high level wrapper around GRC
.
Implementation
My implementation is based on GDC
as it is low level and introduces minimal amount of overhead.
While porting of Android
part is straightforward cancellation and context identification API was missing in CocoaTouch
. Following API were added as part of iOS14.5 bindings:
dispatch_block_*
for scheduled block cancellation;dispatch_queue_(get|set)_specific
,dispatch_get_current_queue
to allow tagging and identification of dispatch_queue as context.
Dispatch with cancellation implemented as bellow:
override fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle {
val dispatchBlock = DispatchBlock.create(DispatchBlockFlags.None, block)
dispatchQueue.after(timeMillis, TimeUnit.MILLISECONDS, dispatchBlock)
return object : DisposableHandle {
override fun dispose() {
DispatchBlock.cancel(dispatchBlock)
}
}
}
And identification:
override fun isDispatchNeeded(context: CoroutineContext): Boolean {
return !invokeImmediately || DispatchQueue.getCurrentSpecific(DispatchQueueAssociatedValues.key) !=
dispatchQueue.getSpecific(DispatchQueueAssociatedValues.key)
}
Source code
Source code is available in dkimitsa/kotlinx.coroutines.robovm.
Also it was built and deployed to sonatype
maven repository and ready for use as dependency:
repositories {
maven { url 'https://oss.sonatype.org/content/repositories/snapshots' }
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlin_version"
implementation "io.github.dkimitsa.robovm:kotlinx-coroutines-robovm:0.1-SNAPSHOT"
}
Things to note
At moment of writing following pull request are not merged and required to be pulled in. And local version of RoboVM to be built from source code.
- iOS14.5 bindings;
- if deebugger used: SMAP parser support for kotline 1.4/1.5.
Comments