WWDC Quick Look 💓 By SwiftGGTeam
Explore Swift and Java interoperability

Explore Swift and Java interoperability

观看原视频

Highlight

SwiftJava 通过 JavaKit 宏、SwiftKit 库和 swift-java 命令行工具,实现 Swift 实现 JNI native 方法、从 Swift 导入 Java 库、把 Swift 库封装为 Java 库三种互操作方向。


核心内容

很多团队的核心业务逻辑跑在 Java 上。要把 Swift 引入这种代码库,过去只有一条路:手写 JNI。JNI 从 1997 年就存在,比 iPod 还早,API 几乎没变过。开发者要先在 Java 端声明 native 方法,再用 javac -h 生成 C 头文件,然后手动实现 Java_com_example_JNIExample_compute 这种长名字函数,处理 JNIEnvjobject、对象生命周期。Konrad 在 Session 里说得很直白:能用,但很难用对,签名拼错就崩溃。

WWDC25 上 Apple 把 SwiftJava 作为官方 interoperability 项目正式介绍。它包含三块:Swift 端的 JavaKit(库 + 宏,简化 JNI 调用)、Java 端的 SwiftKit(Java 应用使用 Swift 对象的辅助库)、以及 swift-java 命令行工具和 SwiftPM 插件。整体设计目标是让 Swift 既能从 Java 调过去,也能反过来被 Java 调用——而且两种方向都能在同一个项目里共用。

SwiftJava 解决三类典型场景。第一种:Java 应用里某个 native 方法用 Swift 实现,靠 JavaKit 宏替代 C 头文件。第二种:从 Swift 里整体导入 Java 库,靠 swift-java 工具调用 Gradle 解析依赖。第三种:把整个 Swift 库打包成 Java 库分发,底层走 Java 22 起稳定的 Foreign Function and Memory API,绕开 JNI。三种场景对应三种工具组合,可以独立使用,也可以混搭。


详细内容

Swift 实现 JNI native 方法

第一种场景从 Java 端 native 方法开始。Konrad 说 Swift 和 Java 在运行时层面其实非常相似——类继承模型、自动内存管理(ARC vs GC)、泛型系统、错误抛出(Swift Error vs Java Exception)都对应得上。所以让 Swift 实现 Java 的 native 方法是合理的切入点。

工具链流程是:用 swift-java 命令行工具替代 javac -h,工具会读取 Java 类生成 Swift 端的桥接代码,包括一个 JNIExampleNativeMethods 协议——它取代了原本的 C 头文件。开发者只需要写一个 extension,conform 这个协议,加上 @JavaImplementation@JavaMethod 宏即可(09:05)。

import JavaKit
import JavaRuntime

import Crypto

@JavaImplementation("com.example.JNIExample")
extension JNIExample: JNIExampleNativeMethods {
 
  @JavaMethod
  func compute(_ a: JavaInteger?, _ b: JavaInteger?) -> [UInt8] {
    guard let a else { fatalError("Expected non-null parameter 'a'") }
    guard let a else { fatalError("Expected non-null parameter 'b'") }
    
    let digest = SHA256Digest([a.intValue(), b.intValue()]) // convenience init defined elsewhere
    return digest.toArray()
  }
}

关键点:

  • @JavaImplementation("com.example.JNIExample"):宏标注当前 extension 实现的是 Java 端 com.example.JNIExample 类的 native 方法,工具据此生成 JNI 入口符号。
  • extension JNIExample: JNIExampleNativeMethodsJNIExampleJNIExampleNativeMethods 协议都由 swift-java 工具生成,协议列出了所有需要 Swift 实现的 native 方法。
  • @JavaMethod:宏处理 JNI 细节(JNIEnv 提取、参数 unbox、返回值 box)。
  • JavaInteger?:Java 端的 Integer 是对象,可能为 null,所以映射为 Swift 可选类型;用 intValue() 取出原始 int。
  • import Crypto:Swift 实现里可以直接 import 任何 Swift 库——这里用 swift-crypto 算 SHA256,省去了在 Java 里调原生加密库的麻烦。

从 Swift 导入 Java 库

第二种场景方向反过来:Swift 项目想用一个现成 Java 库(比如 Apache Commons CSV)。Java 库往往有大量传递依赖,靠手写 classpath 不现实。SwiftJava 借用 Gradle 做依赖解析,再生成 Swift 桥接代码(12:30)。

依赖坐标按 Gradle 习惯写成 groupId:artifactId:version 三元组,放在 swift-java.config 里。解析有两条路径:用 SwiftPM build plugin 自动触发(但需要关闭 SwiftPM 的安全沙箱),或者手动跑 swift-java resolve 命令把 classpath 写入文件。

swift-java resolve --module-name JavaApacheCommonsCSV

关键点:

  • swift-java resolve:触发依赖解析子命令,在沙箱外运行,避免 SwiftPM 限制。
  • --module-name:指向包含 swift-java.config 的目标模块。
  • 解析结果写入 classpath 文件,后续构建 Swift 模块时自动读取。

解析完之后,Swift 端就可以像用原生模块那样 import Java 库(13:05):

import JavaKit
import JavaKitIO
import JavaApacheCommonsCSV

let jvm = try JavaVirtualMachine.shared()

let reader = FileReader("sample.csv") // java.io.StringReader

for record in try JavaClass<CSVFormat>().RFC4180.parse(reader)!.getRecords()! {
  for field in record.toList()! {      // Field: hello
    print("Field: \(field)")           // Field: example
  }                                    // Field: csv
}

print("Done.")

关键点:

  • JavaVirtualMachine.shared():在 Swift 进程里启动 JVM 实例,Java 代码就跑在同一进程内。
  • FileReader("sample.csv"):直接调用 JDK 自带的 java.io.FileReader,由 JavaKitIO 包装。
  • JavaClass<CSVFormat>().RFC4180:访问 Java 类的静态字段需要通过 JavaClass<T>(),这里取的是 CSVFormat.RFC4180
  • for record in ...:返回的 Java 集合可以直接用 Swift 的 for-each 遍历,背后由 JavaKit 适配 Iterable 协议。
  • 跨语言对象生命周期由 JavaKit 自动管理:必要时把局部引用提升为全局引用,避免 GC 误回收。

把 Swift 库暴露给 Java

第三种场景是最复杂的:把整个 Swift 库当作 Java 库分发。这次不用 JNI,而是用 Java 22 起稳定的 Foreign Function and Memory API(16:22)。

swift-java --input-swift Sources/SwiftyBusiness \ 
           --java-package com.example.business \
           --output-swift .build/.../outputs/SwiftyBusiness \
           --output-java .build/.../outputs/Java ...

关键点:

  • --input-swift:指向要暴露的 Swift 源码目录,工具会解析公开类型、方法、属性。
  • --java-package:生成的 Java 类放进哪个包。
  • --output-swift / --output-java:分别输出 Swift 辅助代码和 Java wrapper 类。
  • 最终产物:Swift 编译为动态库,连同生成的 Java 类一起打包成 Java library,可以直接发布到 Maven 仓库。

生成的 Java 类持有一个 selfMemorySegment 指针,指向 native 堆上的 Swift 实例。Java 端通过 Foreign Function API 直接调用 Swift 函数,不再经过 JNI 转换。

但 Swift struct 是值类型,没有稳定的对象身份,需要分配在堆上才能被 Java 端的指针引用。这就引出内存管理问题:何时释放?Session 给出两种 SwiftArena 方案——Auto Arena 依赖 GC finalizer,简单但 GC 压力大、释放时机不确定;Confined Arena 配合 try-with-resources,作用域结束就立刻释放(18:55)。

try (var arena = SwiftArena.ofConfined()) {
  var business = new SwiftyBusiness(..., arena);
}

关键点:

  • SwiftArena.ofConfined():创建一个限定作用域的内存竞技场,绑定到当前线程。
  • try (...):Java 7 起的 try-with-resources 语法,作用域结束自动调用 close()
  • new SwiftyBusiness(..., arena):构造函数额外接收 arena,Swift 实例在 arena 管理的 native 内存里分配。
  • 块结束 → arena.close() → 销毁 Java wrapper → 销毁 native 上的 Swift 值,时序确定。
  • 优势:不依赖 GC finalizer,避免大量对象时 GC 性能崩溃,也保留了 Swift 程序依赖的有序析构语义。

核心启发

1. 用 SwiftJava 替代手写 JNI

为什么值得做:手写 JNI 真正难的地方在签名拼写、对象生命周期管理、缓存优化等细节,签名拼错就直接崩溃。JavaKit 宏把这些都收进编译期检查,崩溃风险显著降低。

怎么开始:在已有 Java 项目里挑一个性能敏感、逻辑简单的 native 方法(比如哈希、压缩),用 swift-java 工具生成桥接代码,写一个 extension 实现该方法,对照原版 benchmark 验证。

2. 用 swift-java resolve + Gradle 引入 Java 生态库

为什么值得做:很多领域(CSV 解析、Apache 系列、Hadoop、企业级中间件)Swift 没有对等库。靠 SwiftJava 直接导入 Java 库,可以省掉重写或找替代品的成本。

怎么开始:从单一明确依赖入手(比如 Apache Commons CSV),写好 swift-java.config 里的依赖三元组,跑 swift-java resolve 命令产出 classpath,再在 Swift 代码里 import JavaXxx 试用 API。

3. 把核心业务逻辑用 Swift 实现,再封装成 Java 库

为什么值得做:团队里 Java 项目、Android 项目、服务端 Java 微服务都需要同一份核心规则。用 Swift 写一次,靠 swift-java 工具生成 Java wrapper,所有 Java 项目都能消费——同时为后续把更多模块迁到 Swift 留好接口。

怎么开始:选一个边界清晰的领域模型(账单计算、风控规则、协议编解码),在 Swift 里用 struct 表达,跑 swift-java 命令生成 Java 包,发到内部 Maven 仓库供其他团队使用。

4. Java 调 Swift 一律用 Confined Arena + try-with-resources

为什么值得做:Auto Arena 依赖 GC finalizer,对象数量一大就拖垮 GC,且释放时机不可预测。Confined Arena 把生命周期还给开发者,对应 Swift 的确定性析构语义。

怎么开始:把所有 Swift 对象的创建都放在 try (var arena = SwiftArena.ofConfined()) 块里,作用域结束自动释放。压测时观察 GC 日志和延迟分布,对比 Auto Arena 模式。

5. 关注 SwiftJava 项目本身

为什么值得做:项目处于早期,Gradle 集成还在打磨,命令行体验还在迭代。早期参与者能影响 API 设计,也容易在文档/示例上做出贡献。

怎么开始:clone github.com/swiftlang/swift-java,跑通官方 example,把遇到的问题去 Swift 论坛或 GitHub 提 issue。


关联 Session

评论

GitHub Issues · utterances