1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Rust + FFIでiOS向けHTTPクライアントライブラリを作成する

Posted at

概要

RustでHTTPクライアントライブラリを作成して、それをXcodeからFFIで呼び出す方法です。
JSONPlaceholderのposts/1からJSONデータを受け取って構造体にしてコールバックを返す関数を作ります。
reqwestクレートを使用して実装します。
私が学習のために実装したの内容ですので、間違いが含まれているかもしれません。

方法

プロジェクト作成

ライブラリとしてプロジェクトを作成します。

$ cargo new httpclient --lib

静的ライブラリとして作成するよう設定します。

Cargo.toml
[lib]
crate-type = ["staticlib"]

実装

HTTP通信処理

依存追加

reqwestを使用してHTTPクライアントを実装します。
Cargo.tomlのdependenciesにreqwestとserde追加します。
serdeは、受け取ったJSONデータを構造体にデシリアライズするのに使います。

Cargo.toml
[dependencies]
reqwest = { version = "0.11", features = ["json", "blocking"] }
serde = { version = "1.0", features = ["derive"] }

HTTP通信処理追加

受け取ったJSONデータを受け取る構造体と、reqwestを使用したHTTP通信処理を追加します。

lib.rs
use serde::Deserialize;

#[derive(Debug, Deserialize)]
struct PostInternal {
    id: u32,
    #[serde(rename = "userId")]
    user_id: u32,
    title: String,
    body: String,
}

fn get_request_impl() -> Result<PostInternal, Box<dyn std::error::Error>>{
    let post = reqwest::blocking::get("https://jsonplaceholder.typicode.com/posts/1")?
        .json::<PostInternal>()?;
    Ok(post)
}

serde(rename = "userId")属性は、JSONデータのキー名と構造体のメンバ名が違う時に対応づけるために追加します。
Rustは構造体のメンバ名をsnake_caseにするよう推奨しているので、このようにしています。

FFI

データ構造体追加

C++側で使用する構造体を追加します。

lib.rs
use std::ffi::{c_char, c_uint, CString};

#[repr(C)]
pub struct Post {
    id: c_uint,
    user_id: c_uint,
    title: *const c_char,
    body: *const c_char,
}

コールバックインタフェース追加

通信完了時にコールバックとして呼び出すインタフェースを追加します。

lib.rs
type RequestCallback = extern "C" fn(bool, *const Post);

FFI用関数追加

先ほど追加したget_request_impl関数を呼び出して、C++向けの構造体に変換してコールバックを呼び出す関数を追加します。

lib.rs
#[no_mangle]
pub extern fn get_request(callback: RequestCallback) {
    thread::spawn(move || {
        let post_internal = get_request_impl();
        match post_internal {
            Ok(p) => {
                let title = CString::new(p.title).unwrap();
                let body = CString::new(p.body).unwrap();
                let post = Post{
                    id: p.id,
                    user_id: p.user_id,
                    title: title.as_ptr(),
                    body: body.as_ptr(),
                };
                callback(true, &post as *const Post);
            }
            Err(e) => {
                println!("get_request error:{}", e);
                callback(false, std::ptr::null_mut());
            }
        }
    });
}

xcframework作成

cbindgenを使用してのヘッダーファイル作成と、ios向け.aファイルを作成するスクリプトを追加します。
unstable-optionsを追加しているのは--out-dirオプションを使うためです。

ビルドターゲットにaarch64-apple-iosなどがない場合は、rustupでツールチェインを追加します。

build.sh
rm -rfv output/

# create header
cbindgen --crate httpclient --output output/include/httpclient.h

# create .a files
cargo build --release --target aarch64-apple-ios --out-dir output/aarch64-apple-ios -Z unstable-options
cargo build --release --target aarch64-apple-ios-sim --out-dir output/aarch64-apple-ios-sim -Z unstable-options

# create xcframework
xcodebuild -create-xcframework \
    -library output/aarch64-apple-ios/libhttpclient.a -headers output/include \
    -library output/aarch64-apple-ios-sim/libhttpclient.a -headers output/include \
    -output output/httpclient.xcframework

上記のスクリプトを実行すると、outputディレクトリ以下にhttpclient.xcframeworkが出力されます。

Xcodeから呼び出し

出力されたxcframeworkを追加して呼び出します。
作成したライブラリで提供しているget_request関数を呼び出して、コールバックで返ってきた情報を表示しています。

ViewController.mm
#import <httpclient.h>

- (IBAction)tapButton:(id)sender {
    get_request( [](bool is_success, const Post* post){
        NSLog(@"request success: %d", is_success ? 1 : 0);
        NSLog(@"title: %s", post->title);
    } );
}

まとめ

以上でRustで作成したiOS向けライブラリをXcodeから呼び出せたと思います。
FFIはメモリ関連で特に注意が必要です。例えばPost構造体はcallbackが呼ばれた後に解放されます。なので、callback外でも参照したい場合はコピーする必要があります。

最初にも書いた通り、上記例には間違いが含まれているかもしれません。
この記事が何かのとっかかりになったらいいなと思います。

参考

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?