【问题标题】:Kotlin File in Native iOS Project with Kotlin/Native带有 Kotlin/Native 的原生 iOS 项目中的 Kotlin 文件
【发布时间】:2018-02-20 17:40:23
【问题描述】:

我想包含一个 Kotlin 文件,该文件仅在现有 iOS 项目中执行数据处理和网络操作,同时保留原生 iOS UI 代码。

虽然我认为 Kotlin/Native 可以实现这一点,但我发现使用 Kotlin/Native 的 iOS 示例 (1,2) 似乎也接管了 iOS UI 代码。

是否可以在不接触 UI 代码的情况下使用 Kotlin/Native 在 iOS 中包含用于数据传输的 Kotlin 文件,如果可以,这样做的步骤是什么?

【问题讨论】:

  • 您还应该查看github.com/JetBrains/kotlinconf-app 了解更多 iOS 示例
  • 感谢您提及此示例。这显然是 Kotlin/Native 可以做的另一个很好的展示。然而,就像我提到的示例一样,它基本上实现了包括 Kotlin 中的 UI 在内的所有内容,而原生 iOS UIViewController 子类和 AppDelegate 基本上是空的。我想保留数千行原生 iOS UI 代码(许多 UIViewController 子类),想知道使用 Kotlin/Native 是否有可能?

标签: ios kotlin native


【解决方案1】:

是的,可以在跨平台项目中使用 Kotlin/Native 在 Kotlin 和原生 iOS UI 代码之间传输数据。这允许基于 Kotlin 的数据模型有一个通用的代码库,而例如继续使用 iOS 的原生 UI 代码。

原始证明:

https://github.com/justMaku/Kotlin-Native-with-Swift 项目为我指明了正确的方向,因为它显示了这样做的基本步骤:

在 Swift UIViewController 中,它调用一个包装函数,该函数应接收来自 Kotlin 函数的字符串。该调用通过 C++ 层进行调解,该层本身启动 Kotlin 运行时,将请求传递给 Kotlin 函数,从它接收字符串,然后将其传递回 Swift UIViewController,然后显示它。

在技术层面,该项目包含一个脚本,将 Kotlin、C++ 和 Kotlin/Native 部分编译成一个静态库,然后可以从原生 iOS 项目中调用。

为了让代码运行,我必须(从 gi​​t 克隆后)在运行“./setup.sh”之前执行“git submodule sync”。

要使用基于 Kotlin 的数据模型传输数据,我想要一个通用函数,它可以将数据传递给 Kotlin,修改该数据,并将结果返回给原生 iOS 代码。作为可以构建这样一个函数的原理证明,我将项目扩展为不仅从 Kotlin 接收一个字符串,而且将一个字符串发送到 Kotlin,附加它,然后将结果发回。

项目扩展:

由于在这个看似简单的扩展中存在一些障碍,因此我为任何感兴趣的人列出了步骤。如果你跟着,你应该得到以下显示:

文字可能很愚蠢,但它会告诉你会发生什么。 函数viewDidAppear中ViewController.swift的变化是:

    let swiftMessage: String = "Hello Kotlin, this is Swift!"
    let cStr = swiftMessage.cString(using: String.Encoding.utf8)
    if let retVal = kotlin_wrapper(cStr) {
        let string = String(cString: retVal)
        ...
    }

您会在包装函数中看到 Swift 发送给 Kotlin 的文本(最后,将显示生成的“字符串”变量)。可以直接将 Swift 字符串传递给包装器,但我想强调的是,包装器会将输入和输出视为 c 字符串。事实上,原生 iOS 项目中的 Kotlin Native-Bridging-Header.h 文件现在变为:

extern const char* kotlin_wrapper(const char* swiftMessage);

它转到文件 Launcher.cpp。由于 original file 使用 KString 作为 kotlin_main 的结果值,我尝试了一段时间 convert const char* to KString 并将其传递给 kotlin_main。最后我发现,直接将 const char* 变量转移到 Kotlin 中,并使用 Kotlin/Native 提供给我们的函数进行转换,要简单得多。

我的 Launcher.cpp 然后变得比原来的更紧凑。这是完整的文件:

#include "Memory.h"
#include "Natives.h"
#include "Runtime.h"
#include "KString.h"
#include <stdlib.h>
#include <string>

extern "C" const char* kotlin_main(const char* swiftMessageChar);

extern "C" const char* kotlin_wrapper(const char* swiftMessageChar) {
    RuntimeState* state = InitRuntime();
    if (state == nullptr) {
        return "Failed to initialize the kotlin runtime";
    }
    const char* exitMessage = kotlin_main(swiftMessageChar);
    DeinitRuntime(state);
    return exitMessage;
}

您会看到包装器如何首先启动 Kotlin 运行时,然后调用位于文件 kotlin.kt 中的函数 kotlin_main:

import konan.internal.ExportForCppRuntime
import kotlinx.cinterop.CPointer
import kotlinx.cinterop.ByteVar
import kotlinx.cinterop.cstr
import kotlinx.cinterop.nativeHeap
import kotlinx.cinterop.toKString

@ExportForCppRuntime

fun kotlin_main(cPtr: CPointer<ByteVar>): CPointer<ByteVar> {
    val swiftMessage = cPtr.toKString()
    val kotlinMessage = "Hello Swift, I got your message: '$swiftMessage'."
    val returnPtr = kotlinMessage.cstr.getPointer(nativeHeap)
    return returnPtr
}

指针被转换为 Kotlin 字符串,然后用于创建 kotlinMessage(数据转换的示例)。然后将结果消息转换回指针,并通过包装器传递回 Swift UIViewController。

从这里到哪里去?

原则上,无需再次接触 C++ 层即可使用此框架。只需定义打包和解包函数,将任意数据类型打包成一个字符串,然后将字符串解包到另一端的相应数据类型。这样的打包和解包函数必须为每种语言编写一次,并且可以在不同的项目中重复使用,如果做得足够通用的话。在实践中,我可能会先重写上面的代码来传递二进制数据,然后编写 pack 和 unpack 函数来将任意数据类型转换为二进制数据。

【讨论】:

  • 我可以在 ios 的 kotlin/native 中使用期望和实际类声明吗?
【解决方案2】:

如果需要,您可以将 kotlin 用作框架,因此 kotlin 代码保留在框架文件中,这样您就可以在 android 和 iOS 上使用一些通用代码,而无需在 kotlin 中编写完整的 iOS 应用程序。

使用 gradle 在 objc/swift 兼容的框架中构建你的 kotlin 代码

在你的 build.gradle 文件中

buildscript {
    ext.kotlin_native_version = '0.5'
    repositories {
        mavenCentral()
        maven {
            url "https://dl.bintray.com/jetbrains/kotlin-native-dependencies"
        }
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-native-gradle-plugin:$kotlin_native_version"
    }
}
group 'nz.salect'
version '0.1'
apply plugin: "konan"
konan.targets = ["iphone", "iphone_sim"]
konanArtifacts {
    framework('nativeLibs')
}

它将生成两个 .framework 文件,一个用于模拟器,另一个用于实际设备,将框架放入您的项目中,并将其与任何其他第三方框架一样链接到您的项目。

Cmd: ./gradlew build

注意:每次您更改 kotlin 文件时,都会构建和替换您的 框架文件(您可以创建一个 shell 脚本并将其添加到 构建阶段以自动执行此操作)。

干杯!!!

【讨论】:

  • 是否可以使用这种方法来使用期望/实际类声明?如果是,那么如何?
猜你喜欢
  • 1970-01-01
  • 2020-10-25
  • 1970-01-01
  • 2020-07-19
  • 2018-10-06
  • 2023-01-26
  • 2018-02-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多