Swift 3 の Data とポインタ使いこなし術

この記事は、はてなエンジニアアドベントカレンダー2016の8日目の記事です。昨日は id:ichirin2501 による MySQL-5.6のMRRにデッドロック回避の夢を見る - ichirin2501's diary でした。

おはようございます。シニアアプリケーションエンジニアの id:cockscomb です。皆さんは Swift でバリバリ開発していますか。

9月に Swift 3 が正式にリリースされ、弊社としてリリースしているアプリもほとんどが Swift 3 に移行済みです。Swift 3 への移行はスムーズでしたが、それでも気を遣う部分が多くありました。中でも SE-0069 Mutability and Foundation Value TypesSE-0086 Drop NS Prefix in Swift Foundation によって議論された、値型の Foundation への置き換えには悩まされる場合がありました。また Swift 3 の標準ライブラリでは SE-0107 UnsafeRawPointer APISE-0138 UnsafeRawBufferPointer (Swift 3.0.1) などの提案でポインタ周りも改善されました。これらは Migration Guide が別に作成されるほどです。本記事は Swift 3 の値型の Data やポインタについて少しでも理解を深めようというものです。

Foundation と Data

Foundation フレームワークは、基本的な機能を提供するコアライブラリの一部です。そもそも Objective-C の時代に用意されたものですが、Swift 3 ではオープンソース化やマルチプラットフォーム化に伴い、Swift により良くマッチするように変化しました。Swift の値型のパラダイムに合わせて、参照型の NSDate class に対して値型の Date struct が付け加えられました。これらでは Swift らしくなるようにインターフェースが変更されている場合もあります。

Foundation の Data は、バイト列を表現するための型です。旧来の NSDataNSMutableData に対応します。Data は1バイトを表す UInt8 型の列としても振る舞い、また内部のメモリ領域を指すポインタを触ることができます。

Swift 標準ライブラリのポインタ

Swift においてポインタは、言語が保障しているはずの安全性をいくつかの方法で破壊可能な存在です。それでも Swift 標準ライブラリは、このポインタをいくらかでも安全な方法で扱えるようにしています。Swift 3 ではさらに整理され、より扱いやすくなりました。

Swift からバイト列やポインタを操作するような場面について、あまりイメージが湧かないかもしれません。iOS のアプリを作っていると、画像を扱う場合でも直接このような方法でバイナリを操作することは稀で、Core Graphics フレームワークなどを使います。しかし C で書かれたライブラリを利用する場合などでは、どうしてもバイナリやポインタの操作が必要になってきます。実際に LGTM Camera のアプリ内購入部分で、レシートのバリデーションや ASN.1 データ構造のパースに OpenSSL の機能を利用しており、ポインタが多用されています。

Swift のポインタ

Swift にはポインタを表す型が数多くあります。これらの多くは UnsafeXxxPointer といった名前を持っています。名前が Unsafe で始まっていて、なんだか危険なニオイがします。それぞれの基本的な性質は以下のマトリックスのようになっています。

Mutable Typed Collection
UnsafePointer<Pointee> ❌️ ⭕️ ❌️
UnsafeMutablePointer<Pointee> ⭕️ ⭕️ ❌️
UnsafeRawPointer ❌️ ❌️ ❌️
UnsafeMutableRawPointer ⭕️ ❌️ ❌️
UnsafeBufferPointer<Element> ❌️ ⭕️ ⭕️
UnsafeMutableBufferPointer<Element> ⭕️ ⭕️ ⭕️
UnsafeRawBufferPointer ❌️ ❌️ ⭕️
UnsafeMutableRawBufferPointer ⭕️ ❌️ ⭕️

Mutable はポインタの指している先を変更可能かどうかで、Typed はポインタが型を持っているかどうか、CollectionCollection protocol を実装しているかどうかです。Raw の付かない型を持っているポインタでは、alignment を考慮して(MamoryLayout<Pointee> を考慮して)つぎつぎと値を読み取っていくことができます。Buffer の付くポインタは、先頭のアドレスからいくつ分の要素が含まれているかの情報を持つため Collection を実装できていて、使い勝手がよさそうです。

(これ以外にもポインタはありますが、基本的にはこの8つを利用します。)

UUID を Data にする

少し抽象的なので、実際の例として UUIDData に変換してみます。func data(from uuid: UUID) -> Data というインターフェースでいくつかのパターンを実装してみましょう。

NSUUID と NSMutableData

UUIDNSUUID として取り扱うと func getBytes(UnsafeMutablePointer<UInt8>!) というメソッドが利用できます。また NSMutableDatavar mutableBytes: UnsafeMutableRawPointer プロパティも使ってみます。

func data(from uuid: UUID) -> Data {
    let data = NSMutableData(length: 16)!
    (uuid as NSUUID).getBytes(data.mutableBytes.assumingMemoryBound(to: UInt8.self))
    return data as Data
}

UnsafeMutableRawPointer を型付きの UnsafeMutablePointer<UInt8> にするために assumingMemoryBound(to:) を呼び出していますが、それ以外は単純です。Swift 3 より前の古典的な方法に近いでしょう。mutableBytes プロパティのポインタは NSMutableData と同じ寿命を持っているので、使い回してはいけません。

NSUUID と Data その1

func data(from uuid: UUID) -> Data {
    var data = Data(count: 16)
    data.withUnsafeMutableBytes { (pointer) in
        (uuid as NSUUID).getBytes(pointer)
    }
    return data
}

DatawithUnsafeMutableBytes(_:) メソッドを使うともう少しマシに書けます。このメソッドに渡すクロージャの外側ではポインタが無効であることが明確で、メモリ管理の上で安全です。

NSUUID と Data その2

func data(from uuid: UUID) -> Data {
    let pointer = UnsafeMutablePointer<UInt8>.allocate(capacity: 16)
    defer {
        pointer.deallocate(capacity: 16)
    }
    (uuid as NSUUID).getBytes(pointer)
    return Data(bytes: UnsafeRawPointer(pointer), count: 16)
}

先にポインタを作って allocate してから Data を作る方法です。Data のイニシャライザで内容をコピーしているので、allocate した分は解放する必要があります。Swift の defer が役に立つ場面です。

UUID と Data

extension UUID {
    var data: Data {
        return Data(bytes: [
            uuid.0, uuid.1, uuid.2, uuid.3, uuid.4, uuid.5, uuid.6, uuid.7,
            uuid.8, uuid.9, uuid.10, uuid.11, uuid.12, uuid.13, uuid.14, uuid.15,
        ])
    }
}

func data(from uuid: UUID) -> Data {
    return uuid.data
}

最後は UUIDuuid: uuid_t を使うパターンで、uuid_tUInt8 が16個並んだタプルになっているので、Data のイニシャライザにバイト列として渡すことができます。いちばんシンプルで好ましいですね。

まとめ

Swift におけるポインタや Foundation の Data について概観しました。これらは Swift を利用した高レベルのプログラミングではあまり登場しませんが、C のライブラリとの連携など低レベルな部分では必要になります。特にサーバーサイド Swift においては、ミドルウェアとの連携のために必要になることが多くなるでしょう。

Data やポインタを使いこなしてスイスイ Swift していきましょう!

スイスイSwift! バリバリがんばり! 大・開・発!!!

明日のアドベントカレンダーは id:dekokun です。