Ios_Interop

Apple Interoperation

Intro

The whole point of using KMP is to write less redundant code on each platform. So in this document we can see how to integrate with apple ecosystem or iOS specific implementation.

Documentation

kotlin | ios integration methods

kotlin | native obj-c interop

Integrations

Plugins

Add Kotlin KMP plugins in android studio IDE

  • Kotlin Multiplatform
  • Compose Kotlin Multiplatform

gradle Build

Run the build command in Android studio embedded terminal to build the artifacts for KMP lib for iOS xcode.
It can take 2 - 5 mins depending on the size of the project / hardware.

./gradlew :proj_directory:lib_name:embedAndSignAppleFrameworkForXcode

gradle tasks

You can have tasks available to u.
eg. unit tests / xcode specific ? or platform specific

./gradlew tasks

// -- list (quick MAN page)

Files

build.gradle.kts - Current library gradle build settings
gradle.properties - Current project gradle properties
.xcodeproj - xcode | iOS specific build project file format
Package.swift - SPM package - swift package manager spm dependency format

iOS Build

gradle properties

iOS specific project
Need to update gradle.properties

#Targets to build:  
build.Android=true  
build.iOS=true  // iOS Specific
build.browser=false  
build.native=false

jetbrains | multiplatform-integrate-in-existing-app

run iOS app config

You need to add the iOS configuration to the android project.
You need to provide .xcproj since .xcworkspace was having issues getting the UI scheme to be visible and linkable.
It takes a good minute to resolve those references of xcode proj inside of Android studio config dialog box.

Custom apple dependencies

You can utilize your own apple .xcframework in Android KMP project

You can also reuse other libraries and frameworks from the iOS ecosystem in your iOS source sets. Kotlin supports interoperability with Objective-C dependencies and Swift dependencies if their APIs are exported to Objective-C with the @objc attribute. Pure Swift dependencies are not yet supported.

Probably using cinterop

You can use the cinterop tool to create Kotlin bindings for Objective-C or Swift declarations. This will allow you to call them from the Kotlin code.

kotlin KMP | iOS dependencies framework dependency

Two variants of using cinterop or else you would have to utilize cocoapods

Library mode

kotlin {
    iosArm64() {
        compilations.getByName("main") {
            val DateTools by cinterops.creating {
                // Path to the .def file
                definitionFile.set(project.file("src/nativeInterop/cinterop/DateTools.def"))

                // Directories for header search (an analogue of the -I<path> compiler option)
                includeDirs("include/this/directory", "path/to/another/directory")
            }
            val anotherInterop by cinterops.creating { /* ... */ }
        }

        binaries.all {
            // Linker options required to link to the library.
            linkerOpts("-L/path/to/library/binaries", "-lbinaryname")
        }
    }
}

framework mode

kotlin {
    iosArm64() {
        compilations.getByName("main") {
            val DateTools by cinterops.creating {
                // Path to the .def file
                definitionFile.set(project.file("src/nativeInterop/cinterop/DateTools.def"))

                compilerOpts("-framework", "MyFramework", "-F/path/to/framework/")
            }
            val anotherInterop by cinterops.creating { /* ... */ }
        }

        binaries.all {
            // Tell the linker where the framework is located.
            linkerOpts("-framework", "MyFramework", "-F/path/to/framework/")
        }
   }
}

Singletons

objc-interop | kotlin

Documentation

kotlin {
	targets.withType<org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget> {
	compilations.get("main").compilerOptions.options.freeCompilerArgs.add("-Xexport-kdoc")
	    }
}

kdoc migration to iOS swift / objc

The ability to export KDoc comments to generated Objective-C headers is Experimental. It may be dropped or changed at any time. Opt-in is required (see the details below), and you should use it only for evaluation purposes. We would appreciate your feedback on it in YouTrack.

Limitation

Still generates on Obj-C header type sh!t

swift-interopedia re: data classes -> structs

Data Conversions

Byte array to NS Data

github gist

import kotlinx.cinterop.memScoped
import kotlinx.cinterop.allocArrayOf
import kotlinx.cinterop.addressOf
import kotlinx.cinterop.usePinned
import platform.Foundation.NSData
import platform.Foundation.create
import platform.posix.memcpy

public fun ByteArray.toData(): NSData = memScoped {
    NSData.create(bytes = allocArrayOf(this@toData),
        length = this@toData.size.toULong())
}

public fun NSData.toByteArray(): ByteArray = ByteArray(this@toByteArray.length.toInt()).apply {
    usePinned {
        memcpy(it.addressOf(0), this@toByteArray.bytes, this@toByteArray.length)
    }
}

Code conversions

Additionally, SKIE renames cases that collide with Swift keywords and Objective-C methods. Kotlin also does this, but its implementation is not correct in some cases, which creates a difference in some situations. For example:

Obj-C

Note that the names that collide with Obj-C methods are renamed by adding the the prefix, instead of the _ suffix. So, for example, zone becomes theZone.

Swift

Kotlin (but not SKIE) renames return to return_, which is unnecessary because return is only a soft keyword in Swift.

Swift methods get the _return instead of just the return

enum naming_conventions | touchlabs

Reactive

Flows -> Combine

skie { features 
		{ enableFlowCombineConvertorPreview = true }
}
fun helloWorld(): Flow<String> { } 
let publisher = helloWorld().toPublisher()
publisher.sink { $0 }

SKIE | combine

Async

SKE | suspend | await

Direct Integration

Direct Integration

local integration method

multiplatform-direct-integration

build script

cd "$SRCROOT/.."
./gradlew :shared:embedAndSignAppleFrameworkForXcode

Need to update shared to either sky-auth ? or nitro-apple

cd "$SRCROOT"
# need this coz of the way of the project being structured here
cd ../..
# Runs the actual gradle command which builds the module / lib for 
# xcode to sign or integrate into the project scheme
./gradlew :implementation:skyAuth:embedAndSignAppleFrameworkForXcode

Doc for iOS specific.
make-your-cross-platform-application-work-on-ios

export package

SPM | kmp support

errors

If the build isn't succeeding in android studio locally on the IDE, you can try doing clean & gradle build

Disable JS builds in gradle.properties by setting build.browser=false

- Run `./gradlew common:assemble`
- Run `./gradlew common:publishToMavenLocal`

task not found

SO | KMM: embedAndSignAppleFrameworkForXcode task not found

I think embedAndSignAppleFrameworkForXcode is not supposed to run from the terminal as packForXCode used be.

The proper way to run this task is from XCode build system.

Anyway, I was able to run embedAndSignAppleFrameworkForXcode from terminal after exporting the following variables.

export CONFIGURATION\=Debug
export ARCHS\=x86_64
export EXPANDED_CODE_SIGN_IDENTITY\=-
export FRAMEWORKS_FOLDER_PATH\=iosApp.app/Frameworks
export SDK_NAME\=iphonesimulator15.0
export TARGET_BUILD_DIR\="../build/ios/${CONFIGURATION}-iphonesimulator"

Manual Integration

Xcode search paths

I needed to add /$(CONFIGURATION)/$(SDK_NAME) and make it as non-recursive. Now even if I run app on simulator and then on actual device, Xcode maps & links correct shared.framework. This solved my issue.

That setting is available inside xcodeproj
Targets -> Build Settings -> Framework Sarch Paths

SO | linking issue with kmp iOS

iOS KMP resources

kmp resources for ios dev by touchlab

kmp ios ideal setup guide

Enable “experimental Multiplatform IDE feature” on android studio settings

You will see the Debug icon enabled when iOSApp is selected, but you can only debug Kotlin Code (better than xcode-kotlin).
slack chat

medium | inspect kmms kotlin code on xcode