書いて覚えるSwift入門

第6回サードパーティC/Objective-Cプロジェクトの利用

前回前々回ではそれぞれC(libc)とObjective-C(Foundation)をSwiftから活用してみましたが、どちらもiOS/OS X標準のAPIへのアクセスでした。今回はサードパーティのC/Objective-Cプロジェクトを、Swiftから活用する方法を実例をもとに見ていきます。

swift-gmpint

ご存じのとおり、Swiftにおける整数=Intは、C/Objective-C/C++のint同様、1ワード。64bitプラットフォームなら64bit、32bitプラットフォームなら32bitです。しかしRubyやPythonやHaskellなど、昨今の言語では組み込みの整数を任意精度にする事例が増えています。Swiftでも任意精度の整数を扱えるようにしたいというのは自然の欲求というものでしょう。任意精度整数が組み込みでない言語でその欲求を叶える方法としては、次の2通りが考えられます。

  • A.100%自分で実装
  • B.既存のライブラリを活用

たとえばJavaScriptでは事実上A.の方法しか採れませんが、SwiftならプランB.があります。Mathematicaでも採用されている定番の任意精度整数ライブラリ、GMPを、そのままSwiftから使えるようにしてしまえばいいのです。

そんなわけで作ったのがswift-gmpintです。MacPortsでインストールしたGMPを、Swiftから使えるようにします。

Homebrew派の皆さん、ごめんなさい。ただし未確認ではありますが、本記事中の/opt/local/usr/localにすればもしかして動くかもしれません。fork welcome!

Quick Start

前口上は抜きにして早速試してみたいという読者は、次のようにしてください。

  • ① MacPortsをインストール

  • ② MacPortsからGMPをインストール

    % sudo port install gmp
  • ③ GitHubからswift-gmpintをインストール

    % git clone https://github.com/dankogai/swift-ュgmpint.git
  • ④ Xcodeでプロジェクトをオープン

    % open swift-gmpint/gmpint.xcodeproj
  • ⑤ プロジェクトを実行

標準出力を確認してみてください。2**1024が179769313486231590772930519078902473361797697894230657273430081157732675805500963132708477322407536021120113879871393357658789768814416622492847430639474124377767893424865485276302219601246094119453082952085005768838150682342462881473913110540827237163350510684586298239947245938479716304835356329624224137216であることなどが確認できます。さらにmain.swiftをあれこれ書き換えてみて、任意精度整数演算を楽しんでみてください。

普通のプロジェクトとの違い

それでは、実際のプロジェクトをみてみましょう。まずはプロジェクトファイル図1から。

図1 通常のSwiftプロジェクトとの違い
図1 通常のSwiftプロジェクトとの違い

通常のプロジェクトとの一番の違いは、Cフラグとリンクオプション。-lgmp-I/opt/local/include -L/opt/local/libが追加してあります。ほかは通常のSwiftプロジェクトと変わりません。

次にプロジェクト中のファイルをみてみましょう。

  • main.swift
  • mpz.swift
  • gmp-int-Bridging-Header.h
  • mpz.c

特徴的なのが、mpz.cgmpint-Bridging-Header.h⁠Using Swift with Cocoa and Objective-C]に出てくる三角形の上2つがこれに相当します図2⁠。前々回と前回のプロジェクトではXcode組み込みのファウンデーションを利用していたためこれらは不要でしたが、今回はGMPというサードパーティライブラリを使うため必要となります。

図2 Using Swift with Cocoa and Objective-C]
図2 [Using Swift with Cocoa and Objective-C]

ちなみにgmp-int-Bridging-Header.hは、Xcodeにひな型を生成させることもできますが、* プロジェクト名*-Bridging-Header.hという名前をつけてプロジェクトに手で追加してもOKです図3⁠。

図3 Bridging-Header.hの追加
図3 Bridging-Header.hの追加

それでは、gmp-int-Bridging-Header.hを見てみましょうリスト1⁠。

リスト1 gmp-int-Bridging-Header.h
#include <gmp.h>
void gmpint_seti(mpz_t *op, long i);
void gmpint_sets(mpz_t *op, const char *str, int base);
void gmpint_unset(mpz_t *op);
size_t gmpint_strlen(mpz_t *op, int base);
char *gmpint2str(mpz_t *op, int base);
int gmpint_fits_int(mpz_t *op);
long gmpint2int(mpz_t *op);
int gmpint_cmp(mpz_t *op, mpz_t *op2);
void gmpint_negz(mpz_t *rop, mpz_t *op);
void gmpint_absz(mpz_t *rop, mpz_t *op);
void gmpint_lshift(mpz_t *rop, mpz_t *op, mp_bitcnt_t bits);
void gmpint_rshift(mpz_t *rop, mpz_t *op, mp_bitcnt_t bits);
void gmpint_addz(mpz_t *rop, mpz_t *op, mpz_t *op2);
void gmpint_subz(mpz_t *rop, mpz_t *op, mpz_t *op2);
void gmpint_mulz(mpz_t *rop, mpz_t *op, mpz_t *op2);
void gmpint_divmodz(mpz_t *r, mpz_t *q, mpz_t *op, mpz_t *op2);
void gmpint_powui(mpz_t *rop, mpz_t *op, unsigned long exp);
void gmpint_powmodz(mpz_t *rop, mpz_t *op, mpz_t *exp, mpz_t *mod);

これらの関数名が、Swiftでそのまま使えます。ただしそのまま使えるのは関数名までで、型の名前は読み替えが必要になります。たとえばCintはSwift上でInt32になりますし、char*UnsafePointer<Int8>になります。Cの型名とSwiftの型名の対応表は[Using Swift withCocoa and Objective-C]にも載っていますが、実際にはXcodeのコード補完でいつでも確認できるのでそれほど気にする必要はないでしょう。IDE万歳といったところです。

そしてこれらの関数がmpz.cに実装されているのですが、関数を1つだけ紹介しておきます。

void gmpint_seti(mpz_t *op, int i) {
    mpz_init_set_si(*op, i);
}

見てのとおり、名前を変えているだけです。Cに慣れたプログラマであれば「マクロでいいじゃん」と言いそうですが、コード補完のことを考えるとマクロは用いないほうがよいでしょう。

もうこの時点で、関数だけであればGMPの機能をSwiftから呼び出せるようになっているのですが、せっかくSwiftを使っているのですから、Swift用の型と演算子を用意して、Swift的に使えるようにするべきです。それを実装しているのがmpz.swiftです。まずは型をみてみましょうリスト2⁠。

リスト2 mpz.swift抜粋(その1)
class GMPInt {
    private var mpz = mpz_t()
    init(){ gmpint_seti(&mpz, 0)}
    init(_ mpz:mpz_t) { self.mpz = mpz }
    init(_ s:String, base:Int=10){
        s.withCString {
            gmpint_sets(&self.mpz, $0, Int32(base))
        }
    }
    // to work around the difference between
    // GMP's 32-bit int and OS X's 64-bit int,
    // we use string even for ints
    convenience init(_ i:Int) { self.init(String(i)) }
    deinit {
        gmpint_unset(&mpz)
    }
    func toInt() -> Int? {
        return gmpint_fits_int(&mpz) == 0 ?
            nil : Int(gmpint2int(&mpz))
    }
    var asInt: Int? {
        return self.toInt()
    }
}

まず目につくのが、init()が2種類あること。GMPInt(42)GMP("42")も受け付けます。文字列からの初期化が必要なのは、GMPInt("012345678901234567890123456789012345678901")のように、固定整数には収まらない整数のことを考えれば自明というより、むしろ基本と言ってもいいでしょう。実際固定整数からの初期化init(_ i:Int)の実装は、わざわざString(i)で文字列化しています。コメントにあるとおり、効率を犠牲にして互換性を確保しています。

次の特徴は、deinit()の存在。GMPはCライブラリ[1]⁠ だけあって、メモリ管理が全自動のSwiftとは異なり、メモリの解放を手で行わなければなりません。deinit()はまさにこのような場合のために存在します。Swiftがメモリを解放する際、このメソッドがあれば呼び出されるので、そのタイミングでC側のメモリも解放するようにすればよいわけです。Perlをご存じの読者は、DESTROY相当のメソッドであると覚えておくとよいかもしれません。余談ですが、SwiftもPerlもメモリ管理はリンクカウント方式で、オブジェクトの解放はリンクカウントが0になった段階で行われます。

型定義ができたところで、メソッドと演算子を追加していきますリスト3⁠。

リスト3 mpz.swift抜粋(その2)
extension GMPInt: Printable {
    func toString(base:Int=10)->String {
        let cstr = gmpint2str(&mpz, Int32(base))
        let result = String.fromCString(cstr)
        free(cstr)
        return result!
    }
    var description:String { return toString() }
}

リスト3でふつうにprintln()できるようになり……。

extension GMPInt: Equatable, Comparable {}
func <(lhs:GMPInt, rhs:GMPInt)->Bool {
    return gmpint_cmp(&lhs.mpz, &rhs.mpz) < 0
} func ==(lhs:GMPInt, rhs:GMPInt)->Bool {
    return gmpint_cmp(&lhs.mpz, &rhs.mpz) == 0
}

これで等号不等号が使えるようになりました。

あとは、演算子を定義していくだけです。

/// unary +
prefix func +(op:GMPInt) -> GMPInt { return op }
/// binary +
func +(lhs:GMPInt, rhs:GMPInt) -> GMPInt {
    var rop = GMPInt()
    gmpint_addz(&rop.mpz, &lhs.mpz, &rhs.mpz)
    return rop
}
func +(lhs:GMPInt, rhs:Int) -> GMPInt {
    return lhs + GMPInt(rhs)
}
func +(lhs:Int, rhs:GMPInt) -> GMPInt {
    return GMPInt(lhs) + rhs
}

見てのとおり、二項演算子は片方がIntな場合も受け付けるようにして、なるべくシームレスに演算できるようにしておきましょう。

まとめ

というわけで任意精度整数がSwiftでも使えるようになったのですが、ここで一から実装した例と今回のGMPを活用した例を比較してみましょう図4、図5⁠。

図4 swift-gmpintの行数
    23    130    981 gmpint/gmpint-Bridging-Header.h
    41    117    867 gmpint/main.swift
    64    220   1632 gmpint/mpz.c
   233    827   5800 gmpint/mpz.swift
   361   1294   9280 total
図5 bigint.jsの行数
   560   2368   15429 ../js-math-bigint/bigint.js

bigint.jsはJavaScriptで必要最低限の任意精度整数演算を実装したものですが、JavaScriptだけあって当然演算子などは使えず、たとえば2**1024を計算したければ(new Math.BigInt(2)).mul(1024)などとしなければならないのに対し、SwiftであればGMPInt(2) **1024で事足りてしまいます。どちらが楽かはご覧のとおりです。

Software Design

本誌最新号をチェック!
Software Design 2022年9月号

2022年8月18日発売
B5判/192ページ
定価1,342円
(本体1,220円+税10%)

  • 第1特集
    MySQL アプリ開発者の必修5科目
    不意なトラブルに困らないためのRDB基礎知識
  • 第2特集
    「知りたい」⁠使いたい」⁠発信したい」をかなえる
    OSSソースコードリーディングのススメ
  • 特別企画
    企業のシステムを支えるOSとエコシステムの全貌
    [特別企画]Red Hat Enterprise Linux 9最新ガイド
  • 短期連載
    今さら聞けないSSH
    [前編]リモートログインとコマンドの実行
  • 短期連載
    MySQLで学ぶ文字コード
    [最終回]文字コードのハマりどころTips集
  • 短期連載
    新生「Ansible」徹底解説
    [4]Playbookの実行環境(基礎編)

おすすめ記事

記事・ニュース一覧