【Rust】Rust與Swift相遇…
經過了這麼多年,個人算在iOS上算是小有成就,能混口飯吃,這篇也算是補上之前純Swift編成Framework的一個補充包,只要是能轉成C語言二進制的,通通都可以接在iOS系統上,iOS生可謂是功德圓滿啊,不過呢,最近AI是越來越強大了,像我這種還在寫套件 / 文章的人越來越少了,因為只要問一下AI,程式馬上就出來了,但是個人覺得還是自己寫的最香,就算是用AI問出來的,我還是會去了解,改成自己喜歡的樣子,我對寫程式可算是真愛啊…
做一個長得像這樣的東西
作業環境
| 項目 | 版本 |
|---|---|
| macOS | Sequoia 15.7 |
| Visual Studio Code | 1.108.0 |
| Rust | 1.90.0 |
| Xcode | 16.4 |
建立Rust Library庫
- 因為我們是要做一個套件庫,所以要加上
--lib字樣… - 而裡面會有一個
lib.rs,而不是main.rs
cargo new --lib rust_caesar
凱撒密碼
use std::ffi::{CString, CStr};
use std::os::raw::c_char;
#[unsafe(no_mangle)]
pub extern "C" fn caesar_encrypt(input: *const c_char, shift: u8) -> *mut c_char {
unsafe {
let c_str = CStr::from_ptr(input);
let rust_str = c_str.to_str().unwrap();
let encrypted = _caesar_encrypt(rust_str.to_string(), shift).unwrap();
CString::new(encrypted).unwrap().into_raw()
}
}
#[unsafe(no_mangle)]
pub extern "C" fn caesar_free(ptr: *mut c_char) {
unsafe { let _ = CString::from_raw(ptr); }
}
fn _caesar_encrypt(text: String, shift: u8) -> Result<String, String> {
Ok(text.chars()
.map(|c| if c.is_ascii_uppercase() {
((c as u8 - b'A' + shift) % 26 + b'A') as char
} else if c.is_ascii_lowercase() {
((c as u8 - b'a' + shift) % 26 + b'a') as char
} else { c }
)
.collect()
)
}
- 其中extern “C”,就是把這支func轉成C語言的形式輸出,畢竟Objective-C可以直接接C語言,後面使用起來方便很多…
- 再來就是
#[unsafe(no_mangle)]了,因為在編成二進制後,func的名稱就會改變,有可能是Y2Flc2FyX2VuY3J5cHQ=(),在程式內部是沒問題的,但在外部用的話,這樣子我們就沒辦法知道func名稱,所以加上這個註記,就是在生成後不要改變func名稱,不然要用就麻煩了…
編成二進制標準函式庫 (.a)
- 先在Cargo.toml上加入
crate-type = ["staticlib"],因為我們要編成的是靜態函式庫 - Static Library Archive (.a)嘛…
[package]
name = "rust_caesar"
version = "0.1.0"
edition = "2024"
[lib]
crate-type = ["staticlib"]
[dependencies]
- 然後就給它編譯下去,這裡形式選擇iOS實機
aarch64-apple-ios/ M系列模擬器aarch64-apple-ios-sim的版本… - 在
target資料夾的位置,就可以看到.a檔了,Congratulations !…
cargo build --target aarch64-apple-ios --release
cargo build --target aarch64-apple-ios-sim --release
- 當然如果要編成
x86_64也是可以的,可以使用以下的指令查詢Rust支援的項目列表…
rustc --print target-list
rustc --print target-list | head -1
接下來我們來使用它
- 先把模擬器版的
.a檔加在Swift專案之中… - 再加入一個
Header.h檔,寫出我們要給外部知道哪些是可以用的函數… - 目前可用的函數就是
caesar_encrypt()、caesar_free()兩個…
#ifndef CAESAR_RUST_H
#define CAESAR_RUST_H
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
char* caesar_encrypt(const char* input, uint8_t shift);
void caesar_free(char* ptr);
#ifdef __cplusplus
}
#endif
#endif
再來就是見證奇蹟的時刻
#import "Header.h"
import UIKit
final class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let cString = caesar_encrypt("Hello,World!", 3)
print(String(cString: cString))
}
}
模組地圖 - modulemap
- 但如果每次都要這樣子設定是不是很麻煩?
- 於是就有了
module.modulemap這個的設定檔,把在Xcode要在Build Settings填入的設定,事先就寫好,也一起設定了模組名稱… - 其實在Xcode新增
Bridging-Header.h時,就幫我們做了這個在Build Settings填入的基本設定,不過還是會有其它的設定值要填…
Framework Search Paths = $(SRCROOT)
Header Search Paths = $(SRCROOT)
Library Search Paths = $(SRCROOT)
Other Linker Flags = -force_load librust_caesar.a
- 我們先在Rust中加入
headers資料夾,把Header.h跟module.modulemap放在一起… - 而
module.modulemap中的內容,就是把.h跟.a的位置告訴Xcode,也就是在Build Settings裡面寫的值… - 之所以副檔名取名為
.modulemap,就是指找module的地圖,Xcode根據這張地圖就能找到相對應的檔案了,是不是很厲害啊…
module CaesarRust [system] {
header "Header.h"
link "libcaesar_rust"
export *
}
就是愛xcframework
cargo clean
cargo build --target aarch64-apple-ios --release
cargo build --target aarch64-apple-ios-sim --release
- 先把這些
.a+.h+.modulemap包起來…
xcodebuild -create-xcframework \
-library target/aarch64-apple-ios/release/librust_caesar.a \
-headers headers \
-library target/aarch64-apple-ios-sim/release/librust_caesar.a \
-headers headers \
-output CaesarRust.xcframework
馬上來試用
- 最後,當然就是把打包好的
.xcframework拿來用看看了…
import UIKit
import CaesarRust
final class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let cString = caesar_encrypt("Hello,World!", 3)!
print(String(cString: cString))
}
}
- 其它還有一些關於xcframework的指令,大家可以試用看看…
# 檢查結構
find CaesarRust.xcframework -name "*.a" -o -name "Headers"
# 檢查 .a 內容
lipo -info CaesarRust.xcframework/ios-arm64/libcaesar_rust.a
lipo -info CaesarRust.xcframework/ios-arm64-simulator/libcaesar_rust.a
# 檢查 plist
plutil -p CaesarRust.xcframework/Info.plist | grep -A10 LibraryIdentifier
範例程式碼下載
後記
以前我曾經聽過前輩說,其實程式碼寫得亂的才是大師,因為都是照著他的想法,連續寫下來的,也就是說,程式是有前後關係的;而寫得乾淨的,基本上都是些簡單的程式,才有辦法切得那麼開。後來想想好像也對,像我程式碼分得那麼細,還80%以上有註解的程式碼,真的是少見啊,因為也是要花二倍的時間整理,而在畫面功能上,一點也沒變,真的是浪費時間,但是因為我不是個天才,所以慢工出細活,套件化對我來說,也是減少了再動腦一次的機會,畢竟我不是個聰明人,要再想一次是很累的啊,直接用不是很好嘛…