Kotlin Multiplatform Mobile - Coroutines with delay

How use coroutines with delay in an iOS application

A sample of coroutines dispatcher with delay support for an iOS application.

In case of use ktor as http client you can ignore SSL errors for development environment in the following way:

Example

Shared

import kotlinx.coroutines.CoroutineDispatcher

expect val backgroundDispatcher: CoroutineDispatcher
expect val mainDispatcher: CoroutineDispatcher

Android

import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers

actual val backgroundDispatcher: CoroutineDispatcher = Dispatchers.IO
actual val mainDispatcher: CoroutineDispatcher = Dispatchers.Main

iOS

actual val backgroundDispatcher: CoroutineDispatcher =
    NsQueueDispatcher(dispatch_get_main_queue())
actual val mainDispatcher: CoroutineDispatcher =
    NsQueueDispatcher(dispatch_get_main_queue())

NsQueueDispatcher.kt

import kotlinx.coroutines.CancellableContinuation
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Delay
import kotlinx.coroutines.DisposableHandle
import kotlinx.coroutines.InternalCoroutinesApi
import kotlinx.coroutines.Runnable
import platform.darwin.DISPATCH_TIME_NOW
import platform.darwin.dispatch_after
import platform.darwin.dispatch_async
import platform.darwin.dispatch_get_main_queue
import platform.darwin.dispatch_queue_t
import platform.darwin.dispatch_time
import kotlin.coroutines.CoroutineContext


@OptIn(InternalCoroutinesApi::class)
internal class NsQueueDispatcher(
    private val dispatchQueue: dispatch_queue_t
) : CoroutineDispatcher(), Delay {

    override fun dispatch(context: CoroutineContext, block: Runnable) {
        dispatch_async(dispatchQueue) {
            block.run()
        }
    }

    @InternalCoroutinesApi
    @Suppress("TooGenericExceptionCaught")
    override fun scheduleResumeAfterDelay(
        timeMillis: Long,
        continuation: CancellableContinuation<Unit>
    ) {
        dispatch_after(
            dispatch_time(DISPATCH_TIME_NOW, timeMillis * NANOSECONDS_IN_MILLIS),
            dispatch_get_main_queue()
        ) {
            try {
                with(continuation) {
                    resumeUndispatched(Unit)
                }
            } catch (err: Throwable) {
                println("scheduleResumeAfterDelay ${err.stackTraceToString()}")
                throw err
            }
        }
    }

    @InternalCoroutinesApi
    @Suppress("TooGenericExceptionCaught")
    override fun invokeOnTimeout(timeMillis: Long, block: Runnable): DisposableHandle {
        val handle = object : DisposableHandle {
            var disposed = false
                private set

            override fun dispose() {
                disposed = true
            }
        }
        dispatch_after(
            dispatch_time(DISPATCH_TIME_NOW, timeMillis * NANOSECONDS_IN_MILLIS),
            dispatch_get_main_queue()
        ) {
            try {
                if (!handle.disposed) {
                    block.run()
                }
            } catch (err: Throwable) {
                println("invokeOnTimeout ${err.stackTraceToString()}")
                throw err
            }
        }
        return handle
    }

    companion object {
        private const val NANOSECONDS_IN_MILLIS = 1_000_000
    }
}

Usage

GlobalScope.launch(backgroundDispatcher) {
            println("step 1")
            delay(5000)
            println("step 2")
}

Result

Now you can use function delay() in the iOS application.