昔作ったプログラムをSwiftで書き直す過程で、ポインタを使った処理の書き換えが必要になりました。このときに、どのように書いたかをまとめてみました。今回はその中からバイナリデータを確保する処理についてです。
なお、作って見て思ったのはSwiftで書き直す必要がなければ、ポインタをゴリゴリ使うようなコア部分はObjective-Cのクラスで包んで、Swiftからは呼び出すだけの方が幸せな感じもしました。(画像処理とか音声処理とかは特に)
構造体のメモリ確保
構造体を使った処理ですが、Cではmalloc
関数を使ってメモリ確保したり、スタックに変数で確保し、そのポインタを関数に渡したりします。例えば、次のようなコードです。
#include#include #include struct Rectangle { int32_t x; int32_t y; int32_t width; int32_t height; }; typedef struct Rectangle Rectangle; Rectangle * newRect(int32_t x, int32_t y, int32_t width, int32_t height) { Rectangle *rect = (Rectangle *)malloc(sizeof(Rectangle)); if (rect) { rect->x = x; rect->y = y; rect->width = width; rect->height = height; } return rect; } void printRect(Rectangle *rect) { printf("{%d, %d, %d, %d}\n", rect->x, rect->y, rect->width, rect->height); } int main(int argc, const char * argv[]) { Rectangle *rect = newRect(0, 0, 100, 100); if (rect) { printRect(rect); free(rect); } return 0; }
Swiftでは、Swiftの構造体を定義して、単純にインスタンスを確保するようにしました。関数は構造体そのものに関するものは、構造体のメソッドにして、その他は、関数や他のクラスのメソッドにしました。このCのコードは、構造体のメソッドでも良いと思います。
// Rectangle.swift import Foundation public struct Rectangle { var x: Int32 var y: Int32 var width: Int32 var height: Int32 public func printRect() { print("{\(self.x), \(self.y), \(self.width), \(self.height)}") } }
// main.swift import Foundation let rect = Rectangle(x: 0, y: 0, width: 100, height: 100) rect.printRect()
バイナリの塊が必要なとき
バイナリの塊が必要で、そのために構造体を使っているようなケースのときは、そのバイナリの塊を作る必要があると思います。例えば、ファイルフォーマットやプロトコルのデータ、デバイスとの通信で使用するバイナリデータなどです。
アライメントを考慮せず単純に出力するとき
例えば、上のCで書かれているRectangle
構造体のバイナリデータを作るには、次のようにします。
// main.swift import Foundation var rect = Rectangle(x: 1, y: 2, width: 3, height: 4) let rawPtr = UnsafeRawPointer(&rect) let data = Data(bytes: rawPtr, count: MemoryLayout.size) do { if let desktopURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first { let url = desktopURL.appendingPathComponent("Rectangle.dat") try data.write(to: url) } } catch let error { print(error) }
UnsafeRawPointer
はC/C++ではvoid*
に相当するタイプです。Data
はObjective-CではNSData
に相当するタイプでバイナリデータを格納できます。rect
へのポインタとサイズを指定してrect
の内容を格納したバイナリデータを作っています。アライメントの処理はSwiftの使用任せです。
アライメントを考慮するとき
一般的に、プロトコルやファイルフォーマットはアライメントなども考慮して、バイト単位で格納場所が定義されています。そのため、構造体をそのままバイナリ化するよりも、メンバー単位で出力することの方が無難です。今回のRectangle
では、32bit
符号付き整数が4つ並んでいるだけなので、次のように書けます。
// main.swift import Foundation var rect = Rectangle(x: 1, y: 2, width: 3, height: 4) var data = Data(repeating: 0, count: MemoryLayout.size) var offset = data.startIndex data.replaceSubrange(offset ..< offset.advanced(by: 4), with: &rect.x, count: 4) offset = offset.advanced(by: 4) data.replaceSubrange(offset ..< offset.advanced(by: 4), with: &rect.y, count: 4) offset = offset.advanced(by: 4) data.replaceSubrange(offset ..< offset.advanced(by: 4), with: &rect.width, count: 4) offset = offset.advanced(by: 4) data.replaceSubrange(offset ..< offset.advanced(by: 4), with: &rect.height, count: 4) do { if let desktopURL = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first { let url = desktopURL.appendingPathComponent("Rectangle.dat") try data.write(to: url) } } catch let error { print(error) }
次のような順序で処理を行っています。
- 必要なメモリバッファを確保する
offset
に書き込み先を入れるため、data
の先頭を代入するreplaceSubrange
で、offset
から4バイトをrect.x
で置き換える- 書き込み位置を4バイトずらすため、
advanced
で4バイトずらした位置をoffset
に代入する replaceSubrange
で、offset
から4バイトをrect.y
で置き換える- 書き込み位置を4バイトずらすため、
advanced
で4バイトずらした位置をoffset
に代入する replaceSubrange
で、offset
から4バイトをrect.width
で置き換える- 書き込み位置を4バイトずらすため、
advanced
で4バイトずらした位置をoffset
に代入する replaceSubrange
で、offset
から4バイトをrect.height
で置き換える- デスクトップにファイルを出力する
バイナリデータの作り方は他にもあります。場所によって適切な方法を採用すれば良いと思います。