LoginSignup
10
11

More than 5 years have passed since last update.

Swift で 数値データなどを Data に固める

Last updated at Posted at 2016-10-12

DataStream

頻度はそんなにないものの、時々データをファイルに保存したい時などに、JSON や PropertyListSerialization などでなく バイナリー形式で保存したい時があります。とは言っても、struct の内容をそのまま、ファイルに書き込むのは、エンディアンの違いや、アライメントが異なる場合などにデータ交換に失敗する事が考えられます。

そこで、今回は DataStream というクラスを書いてみたので紹介したいと思います。DataStream には DataReadStream と DataWriteStream がありそれぞれ、読み込み用と書き込み用となっています。この Stream にデータを順時書き込み、または読み込みむ事で Data の形式と交換する事が出来るようになります。

数値データを Data に書き出す

DataWriteStream は書き込み専用です。これの write() で順時データが書き出されます。リトルエンディアンの環境では、ビッグエンディアンに変換されます。書き込み可能なデータの対象は、UInt8, Int8, UInt16, Int16, UInt32, Int32, UInt64, Int64, Float, Double, Bool, Data になります。アライメントは気にする必要はありません。

let writeStream = DataWriteStream()
do {
    try writeStream.write(UInt8(0x01))
    try writeStream.write(UInt16(0x2345))
    try writeStream.write(UInt32(0x6789abcd))

    try writeStream.write(Int8(-120))
    try writeStream.write(Int16(-32000))
    try writeStream.write(Int32(-100_000))

    try writeStream.write(Float(0.5))
    try writeStream.write(Double.pi)

    try writeStream.write(true)
    try writeStream.write(false)
}
catch { ... }

if let data = writeStream.data {
    ...
}

こうして、Dataに固めてから、ファイルに書き出せば、JSON などより効率的に、まとまった数値データなどを保存する事が可能になります。

Data から数値データを取り出す

データの読み込みは、DataReadStreamread() メソッドを呼び出します。データの読み出しに失敗すると、エラーが投げられます。読み込む順番とデータの型は書き込んだ時と一致している必要があります。

let readStream = DataReadStream(data: data)
do {
    let a = try readStream.read() as UInt8 // 0x01
    let b = try readStream.read() as UInt16 // 0x2345
    let c = try readStream.read() as UInt32 // 0x6789abcd

    let d = try readStream.read() as Int8 // -120
    let e = try readStream.read() as Int16 //  -32000
    let f = try readStream.read() as Int32 // -100_000

    let g = try readStream.read() as Float // 0.5
    let h = try readStream.read() as Double // Double.pi

    let h = try readStream.read() as Bool // true
    let i = try readStream.read() as Bool // false
}
catch { ... }

部分的にバイナリデータを扱う場合

文字列を保存する場合などは、文字列をバイナリに変換して保存・読み込みをする必要があります。そのまま保存すると、読み出しの時に何バイト読み込めばいいのか分からないので、そんな場合には読み込むバイト数をヘッダとして書き出した後、実際にバイナリのデータを書き込むといいでしょう。

    let subdata = ...
    try writeStream.write(data.count)
    try writeStream.write(data)

そうすれば、読み込む時に文字列などが含まれるバイナリの部分が何バイトなのかがはっきりします。

    let length = try readStream.read() as Int
    let subdata = try readStream.read(count: length) as Data

任意のデータを書き出す場合・読み込む場合

例えば、特定の構造体のデータを生のまま、書き出したい場合があったと仮定します。

struct MyStruct {
    // ....
}

DataWriteStream のジェネリックの writeBytes() メソッドを呼び出せば、まとめて書き出せます。ただし、エンディアンやアライメントの問題があるので、デバイスやOSを超えてデータ交換する場合には適当ではない可能性があります。

また、StringData をメンバーに持つ場合は、さらに class のインスタンスが含まれる場合も、単純に書き出せるわけではないので、十分気をつけましょう。

    let myStruct: MyStruct ...
    try writeStream.writeBytes(myStruct)

そして、読み込む時は、DataReadStream の ジェネリックなメソッド readBytes() を呼び出します。

    var myStruct = readStream.readBytes() as MyStruct

場合によっては、extension を用意して、利用したい型の読み込み、書き込みをが便利になる場合もあるかもしれません。

extension DataWriteStream {
    func write(_ value: CGPoint) throws {
        let x = Double(value.x)
        let y = Double(value.y)
        try self.write(x)
        try self.write(y)
    }
}

extension DataReadStream {
    func read() throws -> CGPoint {
        let x = try self.read() as Double
        let y = try self.read() as Double
        return CGPoint(x: x, y: y)
    }
}

これで、頻繁に使う型はもう少しばかり便利になります。

    let point: CGPoint = ...
    try writeStream.write(point)
    let point = try readStream.read() as CGPoint

読み込み時に Stream の最後まで来たかどうかの判定

読み込み時に、Stream の最後に来たかどうかの判定は以下のようにします。

    while readStream.hasBytesAvailable {
        // read more
    }

コードは Git Hub から入手可能です。

License

MIT License

10
11
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
10
11