rickclephas / KMP-NativeCoroutines

Library to use Kotlin Coroutines from Swift code in KMP apps
MIT License
1.03k stars 31 forks source link

Add support for Swift to Kotlin cases #42

Open rickclephas opened 2 years ago

rickclephas commented 2 years ago

Currently the whole library is focused on Kotlin to Swift cases but there are also valid cases for Swift to Kotlin support.

For example you should be able to call the following function from Swift:

suspend fun doSomething(something: suspend (String) -> String): String = TODO()

https://kotlinlang.slack.com/archives/C3PQML5NU/p1644904527044239

rickclephas commented 2 years ago

Some thoughts about Swift implementations of Kotlin classes/interfaces.

Kotlin code:

interface A {
    suspend fun foo(): String
    suspend fun bar(): String {
        return "Default"
    }
}

open class AKotlinImpl: A {
    suspend fun foo(): String {
        return "Kotlin"
    }
}

class AKotlinImplNoDefaults: AKotlinImpl {
    override suspend fun bar(): String {
        return "Kotlin"
    }
}

suspend fun main() {
    AKotlinImpl().run {
        println(foo()) // Kotlin
        println(bar()) // Default
    }
    AKotlinImplNoDefaults().run {
        println(foo()) // Kotlin
        println(bar()) // Kotlin
    }
}

Generated Kotlin code:

abstract class ANativeImpl: A {
    final override suspend fun foo(): String { /* ... */ }
    abstract fun fooNativeImpl(): NativeSuspend<String>

    final override suspend fun bar(): String { /* ... */ }
    open fun barNativeImpl(): NativeSuspend<String> { /* ... */ }
}

fun A.fooNative(): NativeSuspend<String> { /* ... */ }
fun A.barNative(): NativeSuspend<String> { /* ... */ }

Swift code:

class ASwiftImpl: ANativeImpl {
    override func fooNativeImpl() -> NativeSuspend<String> {
        return nativeSuspend {
            return "Swift"
        }
    }
}

class ASwiftImplNoDefaults: ASwiftImpl {
    override func barNativeImpl() -> NativeSuspend<String> {
        return nativeSuspend {
            return "Swift"
        }
    }
}

func main() async {
    let impl = ASwiftImpl()
    print(await asyncFunction(for: impl.fooNative())) // Swift
    print(await asyncFunction(for: impl.barNative())) // Default
    let implNoDefaults = ASwiftImplNoDefaults()
    print(await asyncFunction(for: implNoDefaults.fooNative())) // Swift
    print(await asyncFunction(for: implNoDefaults.barNative())) // Swift
}

Note: this won't work because A is an interface and doesn't expose the extension functions like this. Support for interfaces is limited anyway because ObjC doesn't support default implementations.

Note: this way calling the Swift implementations from Swift will convert from async to NativeSuspend and back to async. To fix that we would need an extension/wrapper function in Swift that can directly call the async version instead. That would need some kind of Swift codegen (during Kotlin compile) to make this easy to use.

chris-hatton commented 1 year ago

Just dropping a note to say I too arrived here looking for a library to help with consuming a Swift ASyncStream from Kotlin/Native.

davidgarywood commented 1 month ago

Just dropping a note to say I too arrived here looking for a library to help with consuming a Swift ASyncStream from Kotlin/Native.

Me too! Does anything like this exist ?