在 iOS 上使用 Rust 開發(適意版)
日常碎碎念
上次講了一下在 iOS 平台如何用 Rust 語言開發, 其實總結一下就是將 Rust 代碼編譯成靜態庫文件, 然後提供介面給 Objective-C 調用.
目前存在的問題
之前的做法存在兩個問題.
一個是每次我們更新了 Rust 代碼, 都要重新改一下介面文件.另一個問題就是每次我們都要手動編譯一遍 Rust 代碼.如果這兩樣東西我們都可以自動完成那就完美了.
所以我們要想辦法解決這些問題.
現在先創建個新項目, 之前我們是用 rust-on-ios 這個項目名. 現在也用這個項目名.
mkdir rust-on-ios && cd rust-on-ios
mkdir rust
然後在 rust 目錄下創建一個要編譯的庫的項目, 這些套路跟之前的一樣.
cargo new rs --lib
然後編輯 rs 項目中的 lib .rs 文件
use std::ffi::CString;
use std::os::raw::c_char;
pub fn say_hello() -> *mut c_char {
CString::new("Hello Rust").unwrap().into_raw()
}
這次我們不打算 extern 函數, 因為之後還要用到一個工具.
然後再在同級目錄下創建一個 rs-binding 項目
cargo new rs-binding --lib
然後我們在 rust-on-ios 目錄再創建一個 Cargo .toml 文件, 裡面的內容
[workspace]
members = [
"rust/rs",
"rust/rs-binding"
]
這樣我們就創建了一個 workspace. 接著把 rs-binding 內的 Cargo .toml 文件也修改一下.
[package]
name = "rs-binding"
version = "0.1.0"
authors = ["limit <[email protected]>"]
edition = "2018"
[dependencies]
[dependencies.rs]
path = "../rs"
[lib]
crate-type = ["staticlib"]
順便把 rs-binding 的 lib .rs 文件內容修改一下.
use std::os::raw::c_char;
use std::panic::{self, UnwindSafe};
use std::process;
fn stop_unwind<F: FnOnce() -> T + UnwindSafe, T>(f: F) -> T {
match panic::catch_unwind(f) {
Ok(tmp) => tmp,
Err(_) => process::abort(),
}
}
#[no_mangle]
pub extern "C" fn test() -> *mut c_char {
stop_unwind(rs::say_hello)
}
現在 Rust 這塊算是搞好了, 但是我們還得搞定 iOS 項目.
在 rust-on-ios 目錄下創建個 iOS 項目先, 這裡我直接命名成 ios, 模板選擇 Single View App.
現在項目大概就是這副樣子.

到了這一步, 我們好像沒有寫介面文件, 現在先用 cargo 安裝一個工具.
cargo install cbindgen
這玩意看名字也曉得, 可以用來自動生成頭文件, 附上項目地址.
eqrion/cbindgen
安裝好之後, 再在 rust-on-ios 目錄下創建個 cbindgen .toml 文件, 可以生成 C/Cpp 的頭文件, 這裡用 C 的就好了, 省得 iOS 項目源碼文件還要改成 .mm 副檔名, 但是生成 Cpp 的好處就是可以用 namespace.
autogen_warning = "/* Warning, this file is autogenerated by cbindgen. Dont modify this manually. */"
language = "C"
include_guard = "cbindgen_bindings_h"
[defines]
"target_os = ios" = "TARGET_OS_IOS"
現在在 iOS 項目中創建一個新的 target, 並且設置好 cbindgen 的命令. 我當前的 Xcode 是 9.4, 添加這種 target.


但是這樣 Xcode 並不曉得 cbindgen 這個 bin 在哪可以找到, 所以我們還要給 target 的 Build Settings 加點料.

現在創建了 cbindgen 的自動處理. 之後就是再創建一個 target 用來編譯 Rust 代碼.
這裡我把 target 名設為 rs, 設置好之後, 我們得寫個編譯的腳本, 然後用這個 target 來調用腳本. 腳本我命名成 rust_ios.sh, 並放到 rust-on-ios 目錄下.
#!/bin/sh
for ARCH in `echo $ARCHS`; do
RUST_ARCH="${ARCH}-apple-ios"
if ["${ARCH}" = "arm64"]; then
RUST_ARCH="aarch64-apple-ios"
fi
if ["${CONFIGURATION}" = "release"]; then
CONFIG="--release"
fi
COMMAND="cargo $@ ${CONFIG} --target ${RUST_ARCH}"
echo $COMMAND
$COMMAND
done
因為我只裝了 64 位的 rust target, 所以我們得看看項目中是否設置了 armv7 armv7s 這類.

現在把 rs 這個項目 target 修改一下.

然後設置 target PATH 老套路.
到了現在, 我們還是沒法用 Rust 編譯的東西, 因為我們並沒有把鏈接後的靜態庫文件導入到項目.
這一步是弄一個別名文件, 好處是容易找到靜態庫路徑. 切換到 rust-on-ios 目錄去, 執行命令.
mkdir target-symlinks
cd target-symlinks
ln -s ../target/aarch64-apple-ios arm64
ln -s ../target/x86_64-apple-ios x86_64
現在執行一下 rs 那個 target, 如果是選擇模擬器運行的, 在 x86_64 別名目錄下能看到一個靜態庫文件.
然後把項目的 Library Search Path 設置一下.
由於上面設置了 target-symlinks 所以可以一行東西添加所有的路徑
$SRCROOT/../target-symlinks/$ARCHS/$CONFIGURATION

現在只要添加庫文件就好了, 可以在 Build Phases 的 Link Binary With Libraries 添加, 也可以直接在 General 里添加, 添加靜態庫就不說了.

運行 iOS 項目的時候自動生成頭文件
現在每次我們都需要先生成頭文件, 再去運行 iOS 項目, 這樣很繁瑣, 可以把 target 關聯到 ios 上.

這樣每次運行的時候, 都會自動生成一遍頭文件.
有個注意事項
一般的 Xcode 編譯模式都是首字母大寫的 Debug/Release, 可能會造成運行項目時失敗, 可以修改項目配置文件.

修改這幾個文件內的 Debug Release 改成 全部小寫的, 雖然麻煩, 但是只要改一次就夠了. 因為 Cargo 生成的 target/ 目錄下的文件夾是 debug/release, 由於我們配置的是 $SRCROOT/../target-symlinks/$ARCHS/$CONFIGURATION 這個是按 Xcode 配置來的, 所以需要修改一下.
改完大概長這樣

現在可以自己測試一下了.
好吧, 目前就這樣吧. 之後再考慮有沒有其他玩法.
https://github.com/limitLiu/rust-on-ios推薦閱讀:
TAG:Rust(編程語言) | iOS開發 |
