iOS开发中swift与rust混编

前言

在iOS开发中,希望使用rust来开发部分逻辑以便在不同平台上都可以使用,所以需要研究swift与rust的混编。

使用的方案是 uniffi

rust编译iOS库

新建rust工程

使用 uniffi 生成iOS库, 使用rustup新建rust工程, 在Cargo.toml中添加

[dependencies]
uniffi = { version = "0.28", features = [ "cli" ]  }

[lib]
crate-type = ["lib", "staticlib"]

其中lib代表动态库,staticlib代表静态库,iOS建议使用静态库。

在release下要减少包大小不要忘记开启编译优化:

[profile.release]
lto = "fat"
panic = "abort"
strip = true

新版uniffi可以通过宏自动生成接口,无需在手动写借口文件,如下方的例子:

uniffi::setup_scaffolding!();

#[uniffi::export]
fn say_hi(name: String) -> String {
    format!("Hello, {}!", name)
}

#[derive(uniffi::Object)]
pub struct Greeting {
    pub message: String,
}

生成iOS相关模块文件

cargo build
cargo run --bin uniffi-bindgen generate --library ./target/debug/库文件 --language swift --out-dir ./bindings

这样就会在bindings文件夹下生成 库名称.modulemap 库名称.swift .h文件等iOS库文件需要的辅助文件。将生成的.modulemap文件改名为module.modulemap。

一定要记得将生成的.modulemap文件改名为module.modulemap否则之后会导致Xcode无法正确识别库名称从而swift代码无法import对应的库

之后就可以编译release库,选择对应的平台进行编译,如果编译所有iOS以及macOS平台的话可以如下操作,列出的平台可能不全,比如不包含aarch64-apple-ios-macabi,可以根据rust官方文档自行补充:

for TARGET in \
        aarch64-apple-darwin \
        aarch64-apple-ios \
        aarch64-apple-ios-sim \
        x86_64-apple-ios \
        x86_64-apple-darwin
do
    rustup target add $TARGET
    cargo build --release --target=$TARGET
done

之后可以用lipo把同个平台不同架构的库合并到一起,之后再打包成xcframework就可以给iOS使用了

lipo -create ./target/aarch64-apple-ios-sim/release/库名称.a ./target/x86_64-apple-ios/release/库名称.a -output ./ios/ios-sim/result.a
lipo -create ./target/aarch64-apple-darwin/release/库名称.a ./target/x86_64-apple-darwin/release/库名称.a -output ./ios/darwin/result.a
xcodebuild -create-xcframework \
        -library ./ios/ios-sim/result.a -headers ./bindings \
        -library ./target/aarch64-apple-ios/release/库名称.a -headers ./bindings \
        -library ./ios/darwin/result.a -headers ./bindings \
        -output "ios/库名称.xcframework"

在Xcode中debug rust代码

主要包含以下步骤:

  1. https://github.com/dito010/rust-xcode-plugin 安装到Xcode中。
  2. 安装并使用 https://crates.io/crates/cargo-xcode/ 生成rust对应的Xcode项目文件拖到主工程中,工程中用Add Files to "XXX.xcodeproj" ... 添加上rust源代码。
  3. 根据要debug的平台,将Build Settings中的Base SDK设置为对应的平台,比如iOS就修改为iOS。
  4. 在Build Phases中点击右上角加号New Copy Files Phase,在其中加入生成的module.modulemap和包含的*.h的头文件

这样Xcode就可以正确的生成对应的静态库,并且在运行的时候可以进行debug了。

参考

https://forgen.tech/en/blog/post/building-an-ios-app-with-rust-using-uniffi

https://mozilla.github.io/firefox-browser-architecture/experiments/2017-09-06-rust-on-ios.html

https://github.com/dito010/rust-xcode-plugin

https://juejin.cn/post/6844903975473381389

https://github.com/cmyr/cargo-instruments

https://crates.io/crates/cargo-xcode/

https://mozilla.github.io/uniffi-rs/latest/swift/overview.html

https://github.com/antoniusnaumann/cargo-swift

https://rhonabwy.com/2023/02/10/creating-an-xcframework/ (名字看起来和iOS开发下使用rust和swift混编没什么关系但其实是最详细的一篇)

https://stackoverflow.com/questions/15331056/library-static-dynamic-or-framework-project-inside-another-project

https://stackoverflow.com/questions/32125338/how-can-i-use-an-a-static-library-in-swift/59215981#59215981