見出し画像

iOS 用 Static ライブラリを C で作って Swift で使う


まえがき

C 言語で書いたオリジナルライブラリを、Swift の iOS アプリから呼び出す方法についてです。

C++ の方が書きやすいかとは思いますが、調べた限りでは色々と面倒そうだったので、シンプルにできそうな C を使うことにしました。

まあ信号処理とかだと、C でも C++ でもそう大きくは変わらないと思うので・・。


分かれば簡単なのですが、そのものの情報が見つからず苦労したので書いておきます。


ライブラリソースコード作成

・Mac の Command Line Tool としてプロジェクト生成

まずは Xcode でライブラリのソースコードを作ります。

プロダクト名は例えば myLib とします。

 Create New Project → macOS → Command Line Tool

Product Name:myLib
Language:C

// myLib.c
#include <stdio.h>
#include <stdint.h>
#include <stdbool.h>

int32_t myFunction(bool  flag);

int32_t myFunction(bool flag) {
    if (flag) {
        printf("Hello from myFunction!\n");
    } else {
        printf("Goodbye from myFunction!\n");
    }
    return (int32_t)flag + 1;
}

ライブラリソースコード例


通常の C 言語による開発~デバッグを行っておきます。

必要であれば、デバッグ用のライブラリ呼び出し C コードも一緒に書きます。

ここまではどう作っても構いません。必要なのはソースコードのみです。

Mac 特有のライブラリ等を使わない限り、Windows 上で開発してもよいでしょう。

一部定義ファイルの違い等はありますが、Windows 上でデバッグした後、Mac でエラーだけ対処すれば問題ないかと思います。

他で機能デバッグ済みであれば、 Static ライブラリとして作っても構いません。(run してもビルドのみで実行されないだけ?のようですが)

 Create New Project → macOS → Library

Product Name:myLib
Framework:None
Type:Static

実際、今回仕事用に開発した信号処理ライブラリは、Windows でデバッグまでを行い、Mac では Static ライブラリとしてプロジェクト作成をしました。


ライブラリコンパイル&アーカイブ

ライブラリのソースコードが完成したら、iOS 用ライブラリを生成します。

IDE でコンパイルするとリンクがうまくいかなかったので、コマンドラインツールを使います。


・ライブラリソースがあるフォルダーを Finder で開く

Xcode からであれば、プロジェクトビューのソースコード上で
 右クリック(control+クリック) → "Show in Finder"
で開けます。


・ターミナルで SDK の場所を確認

ターミナルは Finder をカラム表示等にし、フォルダ上で
 右クリック → "フォルダに新規ターミナル"
で開けます。

メニューに項目がない場合は、
 システム設定 →  キーボード → キーボードのショートカット
  → サービス → ファイルとフォルダ 「フォルダに新規ターミナル」
の項目をチェックしておきます。

xcrun --show-sdk-path --sdk iphoneos

xcrun を実行し、表示されるパスを使います。

iOS17.0 では以下のパスでした。
"/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk"

バージョンによって変わります。

xcrun: error: SDK "iphoneos" cannot be located

が出る場合は、

sudo xcode-select --switch /Applications/Xcode.app/Contents/Developer/

としてから xcrum します。 

-isysroot “SDKパス” を指定してコンパイルします。

gcc -c -arch arm64 -isysroot "/Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS17.0.sdk" myLib.c -o myLib.o

.a ファイルにアーカイブします。

ar rcs libmyLib.a myLib.o

これで、iOS アプリから呼び出せるライブラリファイル、libmyLib.a ができました。


Swift 呼び出し側

ライブラリの作成が終わったら、呼び出し側を作ります。

・iOS アプリプロジェクト作成

普通に、iOS アプリのプロジェクトを作成します。

プロジェクト名:SwiftTest


・ライブラリをプロジェクトに追加

.a ファイルをプロジェクトにドロップし、追加するプロジェクトをチェックします。

自動的に Link Binary With Libraries 設定に追加されるので後で確認します。


・Swift 用ブリッジヘッダー作成

ヘッダーファイルを作成し、プロジェクトに追加します。

どう作っても良いですが、例えばプロジェクトビューで
 右クリック → “New File”
で BridgingHeader.h として作成します。

// BridgingHeader.h
#ifndef BridgingHeader_h
#define BridgingHeader_h

// 必要なヘッダーをインクルード
#include <stdint.h>
#include <stdbool.h>

extern int32_t myFunction(bool flag);

#endif /* BridgingHeader_h */

他で作ってプロジェクトにドロップしても構いません。


・Bridging Header 設定

ヘッダーファイルを指定します。

TARGETS Build Settings → Objective-C Bridging Header
に BridgingHeader.h と入れます。

サーチで「bridge」と入れてしまうと出てこないので注意しましょう。
「bridg」もしくは「bridging」です。

Library/ 等フォルダ分けして置きたい場合は 上記 Bridging Header ファイル設定も Library/BridgingHeader.h にします。


・ライブラリの確認

Build Phases → Link Binary With Libraries に libmyLib.a が入っているか確認します。

自動的に入るはずですが、入っていなければ入力します。


・Swift ソース例

 アプリ仕様
-ボタンをタッチで表示が "Hello" 、"Goodbye" に切り替わる
-ボタンの状態(Hello:true 、Goodbye:false)を引数としてライブラリ呼び出し
-ライブラリからは Hello:2、Goodbye:1 が返ってくる
-その返り値によって、ボタンの下にテキスト表示

// ContentView.swift
import SwiftUI

struct ContentView: View {
    // @Stateでトグルの状態を管理
    @State private var isHelloState = true
    
    var body: some View {
        VStack {
            Image(systemName: "globe")
                .imageScale(.large)
                .foregroundStyle(.tint)
            Text("Hello, world!")
                .padding()


            Button(action: {
                // トグルの状態を反転
                isHelloState.toggle()
                print("Button Clicked. Flag is \(isHelloState ? "True" : "False")")
            }) {
                Text(isHelloState ? "Hello" : "Goodbye")
            }
            .padding()
            .background(Color.blue)
            .foregroundColor(.white)
            .cornerRadius(10)
            
            // 条件分岐でViewを返す
            if let message = getMessage() {
                message
            }
        }
    }
 
    // myFunctionの結果に応じたViewを返す
    private func getMessage() -> Text? {
        let ret = myFunction(isHelloState)
        print("return from myFunc : \(ret)")  // デバッグ用表示
        switch ret {
        case 1:
            return Text("Goodbye from myFunc")
        case 2:
            return Text("Hello from myFunc")
        default:
            return Text("No Reply from myFunc")
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}


実機デバッグで動くことを確認します。

SwiftUI 中では、メソッドやクロージャー内以外では print 文が使えずエラーになります。

そのため、getMessage() を呼び出す形にしてその中でデバッグ用に print してます。

print 入れると動かなくなるとか、C っぽいですね。(違)

let _ = Self._printChanges()

辺りも使えるのかな?

あとで勉強します。(u_u)


アプリ動作例

ボタンをタッチする度にボタンのテキストが変わり、ライブラリの返り値によってその下のテキストも変わります。


・Console 出力(デバッグ出力)

デバッグ出力には、ライブラリ内の printf の結果も表示されます。

Hello from myFunction!
return from myFunc : 2

Button Clicked. Flag is False
Goodbye from myFunction!
return from myFunc : 1

Button Clicked. Flag is True
Hello from myFunction!
return from myFunc : 2


あとがき

ライブラリ生成は、IDE 上で行う方法がわからずコマンドラインを使いました。

IDE 上で完結する方法があれば教えてください。m(._.)m


さて、今年の記事 Up は多分これが最後になるかと思います。

皆さん、今年もお世話になりました。

良いお年をお迎えください! (^-^)ノ


タイトル画像モデル:綾夏
The title image was created using Adobe Generative Fill based on this picture.

Original Image

この記事が参加している募集

やってみた

この記事が気に入ったらサポートをしてみませんか?