expect fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
fun getGitHub(): HttpClientCall = runBlocking {
HttpClient().call("https://www.github.com")
}
actual fun <T> runBlocking(context: CoroutineContext, block: suspend CoroutineScope.() -> T): T {
return kotlinx.coroutines.runBlocking(context, block)
}

Xcode hangs forever when invoking the method:
func testRunBlocking() {
GitHubKt.getGitHub()
}
If I delete HttpClient in runBlocking then the method successfully returns a value on iOS.
Versions:
kotlin_version=1.3.0-rc-190
coroutines_version=1.0.0-RC1
ktor_version=1.0.0-beta-2
serialization_version=0.8.2-rc13
Is it a known issue that ktor iOS doesn't work in runBlocking? Is this even a ktor issue or a coroutines issue? https://github.com/Kotlin/kotlinx.coroutines/issues/462
Why do you use runBlocking on ios? You should never block main loop.
Read Guide to UI programming with coroutines first
Unfortunately Dispatchers.Main doesn't work with ios yet. See https://github.com/Kotlin/kotlinx.coroutines/issues/470
I want to block the main loop. I am using this in the context of a testing library that's run in a separate process from the application. Specifically this is an iOS XCUITest.
My confusion is that the code doesn't work. I am not finding any possible way to fix (even using a background thread).
I tried that solution as well, using the custom MainLoopDispatcher and runBlocking never returns when used with ktor.
Well, running coroutines on background threads are not yet supported. Using runBlocking is quite dangerous. And for sure you can't use runBlocking with MainLoopDispatcher and you are already on main loop. Are you sure you actually need blocking?
Are you sure you actually need blocking?
Yeah, I'm replacing an existing blocking networking client (based on gRPC Swift). This is all test code that runs in a different process from the app. It's not related to production at all.
~One possible reason is that the client is trying to resume on main loop while it is blocked~ Also note that a client need to be closed
UPD: looks like ios client is running on unconfined dispatcher
I pushed some fixes:
anything else obviously wrong?
However it makes no difference. ios client is configured to schedule callback to the main loop that is blocked. This is why it doesn't work. So there is no way to use runBlocking with ktor client.
Why can't you simply make getGitHub suspend?
Your fix looks too verbose, you can use use function for client to get it closed
val result = HttpClient().use { it.get<String>("https://.... ") }
there is no way to use runBlocking with ktor client.
Thanks for clarification.
Why can't you simply make
getGitHubsuspend?
How do I call a Kotlin suspend function in a blocking way from Swift? The beauty of runBlocking is the calls are synchronous from the consumer perspective.
When defining getGitHub as suspend, there's no method generated in run_blocking.h From Swift, the method doesn't exist.
suspend fun getGitHub(): HttpClientCall {
return HttpClient().use { it.call("https://www.github.com") }
}
I guess iOS is not working yet?
Currently, there's no way to have coroutines in Objective-C or Swift, so exposing suspend functions look tricky.
Yes, for now there is no way to call suspend functions from swift. However you can invoke swift functions from kotlin coroutines.
ktor client works on ios but you can't mix it with runBlocking and all coroutines need to be launched on a customized coroutine dispatcher that dispatch everything on the main loop
The simplified example:
common.kt
interface MyAppView {
fun onDataLoadComplete(text: String)
}
class MyPresenter(private val view: MyAppView) {
fun load() {
launch {
val result = client.get<String>("http://....")
view.onDataLoadComplete(result)
}
}
}
MyAppViewIos.swift
class MyAppViewIos: .... , MyAppView {
lazy var presenter: MyPresenter = {
MyPresenter(view: self)
}
func somethingClicked() {
presenter.load()
}
func onDataLoadComplete(text: String) { // invoked from presenter's coroutine
// show result
}
}
See complete example here: https://github.com/JetBrains/kotlinconf-app
Relevant files are:
view interface (Kotlin): https://github.com/JetBrains/kotlinconf-app/blob/master/common/src/commonMain/kotlin/org/jetbrains/kotlinconf/presentation/SessionListView.kt
presenter (Kotlin)
https://github.com/JetBrains/kotlinconf-app/blob/master/common/src/commonMain/kotlin/org/jetbrains/kotlinconf/presentation/SessionListPresenter.kt
view implementation (Swift)
https://github.com/JetBrains/kotlinconf-app/blob/master/konfios/konfswift/ui/SessionsViewController.swift
UI dispatcher implementation
https://github.com/JetBrains/kotlinconf-app/blob/master/konfios/konfswift/ui/UI.swift
there is no way to call suspend functions from swift. However you can invoke swift functions from kotlin coroutines.
I think a synchronous REST API defined in Kotlin and called by Swift is blocked then. Probably this will be possible once multithreaded support lands.
The callback approach is interesting. I see that as a good fit for writing apps.
Thanks for all the info.
The async callback code works. With the callback API, it doesn't seem possible to wait for operations to finish. I tried using an operation queue and that crashed.
I defined a custom runBlocking method using NSRunLoop and that works.
https://github.com/bootstraponline/run_blocking/commit/a7953192c7078ab41dbf382f56bdc4432a46c84b#diff-c1a933ca71f0c706f65401b240f8c806
// Expectation.kt
package example
import platform.Foundation.NSDate
import platform.Foundation.NSRunLoop
import platform.Foundation.addTimeInterval
import platform.Foundation.runUntilDate
class Expectation<T> {
private var waiting = true
private var result: T? = null
fun fulfill(result: T?) {
waiting = false
this.result = result
}
fun wait(): T? {
while (waiting) {
advanceRunLoop()
}
return result
}
}
private fun advanceRunLoop() {
val date = NSDate().addTimeInterval(1.0) as NSDate
NSRunLoop.mainRunLoop.runUntilDate(date)
}
// RunBlocking.kt
package example
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.Runnable
import kotlinx.coroutines.launch
import platform.Foundation.NSRunLoop
import platform.Foundation.performBlock
import kotlin.coroutines.CoroutineContext
actual fun <T> runBlocking(block: suspend () -> T): T {
val expectation = Expectation<T>()
GlobalScope.launch(MainRunLoopDispatcher) {
expectation.fulfill(block.invoke())
}
return expectation.wait() ?: throw RuntimeException("runBlocking failed")
}
private object MainRunLoopDispatcher : CoroutineDispatcher() {
override fun dispatch(context: CoroutineContext, block: Runnable) {
NSRunLoop.mainRunLoop().performBlock {
block.run()
}
}
}
Thanks for the idea :)
Most helpful comment
I defined a custom
runBlockingmethod using NSRunLoop and that works.https://github.com/bootstraponline/run_blocking/commit/a7953192c7078ab41dbf382f56bdc4432a46c84b#diff-c1a933ca71f0c706f65401b240f8c806