iOS 編譯和部署 Rust Library

這是一篇譯文,原文連接爲 Building and Deploying a Rust library on iOShtml

首先,咱們須要安裝 Xcode,而後設置 Xcode 編譯工具。若是你已經安裝了 Xcode 編譯工具而且已經將其更新到最新了,你能夠跳過這一步。ios

xcode-select --install
複製代碼

接下來,咱們須要確保安裝了 Rust 環境來編譯 iOS 架構產物。這一步咱們須要安裝 rustup。一樣的,若是你已經安裝了,就能夠跳過這一步。Rustup 安裝工具將安裝 Rust 官方渠道的 release 包而且方便你切換不一樣的 release 版本。這樣有益於你未來的 Rust 開發,而不止因而這篇文章所討論的。git

curl https://sh.rustup.rs -sSf | sh
複製代碼

而後添加 iOS 架構到 rustup,這樣咱們就能編譯跨平臺產物了。github

rustup target add aarch64-apple-ios armv7-apple-ios armv7s-apple-ios x86_64-apple-ios i386-apple-ios
複製代碼

當你安裝 Rust 也會同時安裝 cargo(相似 cocoapods、pip、gems 的 Rust 的包管理工具)。如今咱們使用 cargo 來安裝 cargo-lipo。這是一個 cargo 自動建立 iOS library 的跨平臺子命令。沒有這個 crate,跨平臺編譯 Rust 成 iOS 產物將難於上青天。shell

cargo install cargo-lipo
複製代碼

如今咱們已經完成環境配置準備開始,咱們先來建立項目路徑。swift

mkdir greetings
cd greetings
cargo new cargo
mkdir ios
複製代碼

cargo new cargo 初始化了一個以 cargo 命名的帶有默認文件和路徑的全新的 Rust 項目,cargo 路徑下有個文件叫作 Cargo.toml,這個文件是包管理描述文件(相似 cocoapods 的 podfile),而且這裏有一個子路徑叫作 src,這個路徑下有一個 lib.rs 文件(若是沒有就 touch lib.rs 新建一個),咱們寫的 Rust 代碼都放在這個文件夾下。數組

咱們的 Rust 項目將很是簡單,就是一個 Hello World 項目。它包含一個函數叫作 rust_greeting,這個函數接受一個 string 做爲入參並返回一個 string。所以,假如入參是 「world」,返回值是 「Hello world」。xcode

打開 cargo/scr/lib.rs 複製以下代碼。架構

use std::os::raw::{c_char};
use std::ffi::{CString, CStr};

#[no_mangle]
pub extern fn rust_greeting(to: *const c_char) -> *mut c_char {
    let c_str = unsafe { CStr::from_ptr(to) };
    let recipient = match c_str.to_str() {
        Err(_) => "there",
        Ok(string) => string,
    };

    CString::new("Hello ".to_owned() + recipient).unwrap().into_raw()
}

#[no_mangle]
pub extern fn rust_greeting_free(s: *mut c_char) {
    unsafe {
        if s.is_null() { return }
        CString::from_raw(s)
    };
}
複製代碼

咱們一塊兒來看看上面都寫了啥。app

咱們不可能用 Rust 來調用這個 library,因此咱們用 C 橋接。編譯默認會在編譯時破壞函數名,這裏使用 #[no_mangle] 來告訴編譯器不要破壞函數名,確保咱們的函數名稱被導入到 C 文件。

extern 告訴 Rust 編譯器這個方法將要在 Rust 之外的地方調用,所以要確保其按照 C 的調用規則編譯。

rust_greeting 方法接受一個 C 類型的字符串數組指針,咱們須要把這個 C 字符串轉成 Rust 的 str 類型。首先,咱們用指針建立一個 CStr 對象。而後咱們把這對象轉成 Rust 的 str 類,而後檢查轉換是否成功,假若有錯誤發生,那咱們以 there 代替入參,不然咱們使用入參。而後在入前面拼接一個 Hello,而後返回,返回的 string 咱們須要轉成 CString 而後返回給 C 代碼。

使用 CString 而且返回原始值能保證字符在方法返回之後仍然沒有被釋放。若是字符串被釋放了,指針將會被指向空內存或者徹底是其餘位置。可是爲了確保函數返回之後字符串仍然存在,咱們須要開闢一塊內存,並再也不對這塊內存作任何操做。這是形成內存泄漏的緣由,因此提供第二個函數 rust_greeting_free,函數入參是一個 CString,並負責管理他的內存。咱們須要記得調用 rust_greeting_free 來確保咱們不會在 iOS 平臺碰到問題。

咱們還須要建立 C 橋接文件。 在 cargo / src 中建立一個名爲 greetings.h 的新文件。在這個文件中,咱們來定義一下 C 接口。咱們須要確保 iOS 調用的每一個 Rust 函數都在這裏有定義。

#include <stdint.h>

const char* rust_greeting(const char* to);
void rust_greeting_free(char *);
複製代碼

讓咱們構建咱們的代碼,以確保它能正常工做。 爲了作到這一點,咱們必須完成 Cargo.toml 文件。 這將告訴 cargo 爲咱們的代碼建立一個靜態庫和 C 動態庫。

[package]
name = "greetings"
version = "0.1.1"
authors = ["fluffyemily <fluffyemily@mozilla.com>"]
description = "Example static library project built for iOS"
publish = false
 [lib]
name = "greetings"
crate-type = ["staticlib", "cdylib"]
複製代碼

咱們須要使用 cargo-lipo 構建咱們的 iOS 庫。構建產物位置在 cargo/target/。通用 iOS 庫的位置在 cargo/target/universal/release/libgreetings.a

cd cargo
cargo lipo --release
複製代碼

這就是咱們第一個 Rust 庫,如今咱們把它集成到 iOS 項目中去。

打開 Xcode 並建立一個新項目。 轉到 File\New\Project... 並選擇 iOS Application Single View Application 模板。 這個模板是 iOS 的默認應用程序。 點擊下一步。

咱們以 Greetings 命名咱們的項目,確保建立的是一個 swift 項目。點擊 Next 選擇項目路徑。咱們這裏使用咱們以前建立的 ios 路徑,若是你選擇了其餘路徑,你將不得不修改咱們稍後設置的一些路徑。 點擊建立。

從項目導航器中選擇 Greetings 項目,而後確保選擇 Greetings target。 打開常規選項卡, 向下滾動到 Linked Frameworks and Libraries 部分。

導入你的 libgreetings.a 庫,方法是從 Finder 中拖動它,或者點擊列表底部的 + ,點擊 Add other... 並導航到 cargo/target/universal/release/, 選擇 libgreetings.a,而後點擊 Open。

連接 libresolv.tbd。 點擊 Linked Frameworks 列表底部的 + 並在搜索框中鍵入 libresolv。 選擇 libresolv.tbd,而後「添加」。

須要一個 bridging header 來訪問咱們建立的 C 文件。 首先,讓咱們將 greetings.h 導入到 Xcode 項目中,這樣咱們就能夠連接到它。 轉到 File\Add files to「Greetings.h」 ... 導航到 Greetings.h 並選擇 Add。

要建立 bridging header,請轉到 File\New\File..。 從提供的選項中選擇 iOS Source Header File 並選擇 Next。 將文件命名爲 Greetings-Bridging-Header.h 並選擇 Create。

打開 bridging header 並修改成以下所示:

#ifndef Greetings_Bridging_Header_h
#define Greetings_Bridging_Header_h

#import "greetings.h"

#endif
複製代碼

咱們須要告訴 Xcode 怎麼連接 bridging header。 從項目導航器中選擇 Greetings 項目,而後確保選擇 Greetings target 並打開 Build Settings 選項卡。 將 Objective-C Bridging Header設置爲 $(PROJECT_DIR)/Greetings/Greetings-Bridging-Header.h

咱們還須要告訴 Xcode Rust 庫連接地址。在 Build SettingsLibrary Search Paths 添加 $(PROJECT_DIR)/../../cargo/target/universal/release

按下 Command + R,編譯成功。

如今咱們已經將 Rust 庫導入到咱們的 iOS 項目中,併成功連接。可是咱們尚未調用 Rust 庫。咱們新建一個 swift 文件,命名爲 RustGreetings

添加如下代碼:

class RustGreetings {
    func sayHello(to: String) -> String {
        let result = rust_greeting(to)
        let swift_result = String(cString: result!)
        rust_greeting_free(UnsafeMutablePointer(mutating: result))
        return swift_result
    }
}
複製代碼

這裏建立了一個調用 Rust 庫的新類用做接口。這將爲咱們在 APP 的主邏輯使用提供抽象,不用關心 Rust 庫調用的細節,這些細節包括從 C 字符串到 Swift 字符串的轉換,和咱們不用調用釋放內存的函數也不會出現內存泄漏。

打開 ViewController.swift 中的 viewDidLoad 方法並添加如下代碼。

let rustGreetings = RustGreetings()
print("\(rustGreetings.sayHello(to: "world"))")
複製代碼

如今構建您的項目並運行它。 模擬器將打開並開始運行你的應用程序。 當視圖加載時, Xcode 的控制檯將輸出 「Hello world」。

你能夠在 Github 上找到這個示例工程的代碼。

相關文章
相關標籤/搜索