Swift でタプルを配列に変換する
Swift プログラミング
Swift から C ライブラリを使用していたら、タプルを配列に変換する必要に迫られたので、その方法を探してみました。
Swift で Darwin.C をインポートして C ライブラリを利用していたところ、タプルを配列に変換する必要に迫られました。というのも、今回は C の dirent
構造体を使っていたのですが、ここの d_name
プロパティを使おうとして問題に突き当たりました。
このプロパティには、C 言語では char型 の固定長配列で文字列を持っているのですけど、C の固定長配列は Swift ではタプルとして利用できるようになっています。
Swift のタプルはひとつひとつを分解したり繰り返し処理したりできない様子なので、ひとつひとつ値を取り出さないといけないのですが、この固定長配列は 1024 個もあって、ひとつひとつ処理するにもなかなか無理があります。
そこで、そんなタプルを配列に変換できないかと思って試してみたところ、なんとか無事に変換することに成功しました。
タプルを配列に変換する
今回のタプルを配列に変換するにあたって「タプル内のすべての要素が同じ型であること」を大前提で話を進めます。
とりわけ、今回の場合はすべての要素が CChar型 であるものとします。
タプルの先頭アドレスを取得する
タプルを配列に変換するには、まずはタプルの先頭アドレスを取得する必要があります。
Swift のアドレスと言えば UnsafePointer<T>型 ですけど、この型の機能を使って素直に変換する方法は見つかりませんでした。
ただ、引数に UnsafePointer<Void>型 を取る関数に限って、タプルの参照を渡してあげるとタプルの先頭アドレスを取得できることがわかりました。
そんなわけで、タプルを UnsafePointer<Void>型
型に変換できるまでには至ったものの、今回の例では CChar型
の要素が揃ったタプルなので、扱うときには UnsafePointer<CChar>
にしたいところです。
幸い UnsafePointer<T>
は、任意の型に再解釈できるようになっているので、今回はこれらを利用して、次のようなキャスト関数をひとつ用意しておくことにしました。
func reinterpret<R>(p:UnsafePointer<Void>) -> UnsafePointer<R> {
return UnsafePointer<R>(p)
}
これで、たとえば次のようにしてタプルを任意の型のポインターに変換できます。
let p = reinterpret(&tuple) as UnsafePointer<CChar>
これと似たような UnsafePointer<U>
を引数を取るイニシャライザが UnsafePointer
自身にも用意されていたのですけど、それにタプルを直接渡す方法が見つからなかったので、今回は上記のような関数をひとつ自作しておきました。
先頭アドレスから配列を生成する
タプルの先頭アドレスさえ取得できれば、配列への変換はもうすぐ完成です。
要素の先頭から最後までを順番にたどれる SequenceType
さえ用意できれば配列を作ることができるのですが、先ほどの UnsafePointer<T>
は先頭アドレスしか表現できていないため、そこから最後までたどることはできません。
そこで、先頭からいくつまで情報があるかを表現できる UnsafeBufferPointer<T>
を作ります。
今回の例であれば、固定長配列がタプルになってしまっていたわけで、タプルの要素がいくつ続くかは少なくともわかっているはずです。
それを 変数size
で持っていたとすれば、次のようにして簡単に UnsafeBufferPointer<CChar>
を作れます。
let buffer = UnsafeBufferPointer(start: p, count: size)
こうして作った UnsafeBufferPointer<T>
は SequenceType
になっているので、あとはこれを配列の引数に渡してあげれば、タプルの内容を持った配列ができあがります。
let array = Array<CChar>(buffer)
こうして配列にさえできてしまえば、ひとつひとつを繰り返して処理したり、先頭を切り捨てたり分解したりといったことが自由にできるようになります。
受け取ったタプルをすぐに先頭から順番に処理したいだけなら、配列を作る手前の UnsafeBufferPointer<CChar>
を作るところで止めてそれを使う手もあります。
CChar を扱うタプルを文字列に変換する
ここまでの知識を使うと、固定長の CChar を要素に持つタプルから文字列に変換するのも簡単です。
Swift 2.0 では C 文字列を Swift 文字列に変換するのに String
の fromCString:メソッド
を使いますが、これは引数に UnsafePointer<CChar>
を受け取ります。
先頭を示すポインターだけしか受け取りませんが、C 文字列は末尾が 0 と決まっているので、最後に 0 が現れるところまで読み進めれば良いということになっています。
そのため、上で用意した reinterpret関数 さえ使えば、簡単に文字列に変換できます。
let string = String.fromCString(reinterpret(&tuple))!