この記事は、はてなエンジニアアドベントカレンダー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 Types や SE-0086 Drop NS Prefix in Swift Foundation によって議論された、値型の Foundation への置き換えには悩まされる場合がありました。また Swift 3 の標準ライブラリでは SE-0107 UnsafeRawPointer API や SE-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
は、バイト列を表現するための型です。旧来の NSData
や NSMutableData
に対応します。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 はポインタが型を持っているかどうか、Collection は Collection
protocol を実装しているかどうかです。Raw
の付かない型を持っているポインタでは、alignment を考慮して(MamoryLayout<Pointee>
を考慮して)つぎつぎと値を読み取っていくことができます。Buffer
の付くポインタは、先頭のアドレスからいくつ分の要素が含まれているかの情報を持つため Collection
を実装できていて、使い勝手がよさそうです。
(これ以外にもポインタはありますが、基本的にはこの8つを利用します。)
UUID を Data にする
少し抽象的なので、実際の例として UUID
を Data
に変換してみます。func data(from uuid: UUID) -> Data
というインターフェースでいくつかのパターンを実装してみましょう。
NSUUID と NSMutableData
UUID
は NSUUID
として取り扱うと func getBytes(UnsafeMutablePointer<UInt8>!)
というメソッドが利用できます。また NSMutableData
の var 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 }
Data
の withUnsafeMutableBytes(_:)
メソッドを使うともう少しマシに書けます。このメソッドに渡すクロージャの外側ではポインタが無効であることが明確で、メモリ管理の上で安全です。
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 }
最後は UUID
の uuid: uuid_t
を使うパターンで、uuid_t
は UInt8
が16個並んだタプルになっているので、Data
のイニシャライザにバイト列として渡すことができます。いちばんシンプルで好ましいですね。
まとめ
Swift におけるポインタや Foundation の Data
について概観しました。これらは Swift を利用した高レベルのプログラミングではあまり登場しませんが、C のライブラリとの連携など低レベルな部分では必要になります。特にサーバーサイド Swift においては、ミドルウェアとの連携のために必要になることが多くなるでしょう。
Data
やポインタを使いこなしてスイスイ Swift していきましょう!
スイスイSwift! バリバリがんばり! 大・開・発!!!
明日のアドベントカレンダーは id:dekokun です。