0Pricing
Swift Academy · レッスン

Unsafeバッファポインタ

連続したメモリを効率的に処理します。

「Unsafeバッファポインタ」はCoddyKit上の無料Swift Academyレッスンです。 これはレッスン3/4です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはSwift Academy学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Swift Academyコースには全4レッスンが含まれています。

バッファポインタとは

単一の型付きポインタは、一度に1つの要素を指します。UnsafeBufferPointer<T>は開始ポインタと個数をまとめたもので、Collectionに準拠する連続したメモリ領域への、範囲が限定されたビューを提供します。

つまり、生メモリに対して、おなじみのコレクションAPIである反復処理、サブスクリプト、map、reduceを利用できます。

// A buffer pointer = base address + element count
// UnsafeBufferPointer<Int>(start: ptr, count: 5)

読み取り専用バッファと可変バッファ

単一のポインタと同様に、バッファにも2種類あります。読み取り専用の反復処理にはUnsafeBufferPointer<T>を、要素への代入も必要な場合にはUnsafeMutableBufferPointer<T>を使用します。

let p = UnsafeMutablePointer<Int>.allocate(capacity: 4)
p.initialize(repeating: 0, count: 4)
let buf = UnsafeMutableBufferPointer(start: p, count: 4)
buf[0] = 11
print(buf[0]) // 11

コレクションのように反復処理する

バッファポインタはCollectionなので、forループですべての要素を順番に処理できます。手動でインデックスを計算する必要も、ループ自体でoff-by-oneエラーが発生する心配もありません。ただし、指定した範囲の管理は引き続きご自身の責任です。

let buf = UnsafeMutableBufferPointer<Int>.allocate(capacity: 3)
_ = buf.initialize(from: [10, 20, 30])
for value in buf { print(value) } // 10, 20, 30

コレクションアルゴリズムをそのまま利用する

map、filter、reduce、enumerated、indicesはすべてバッファポインタで動作します。これにより、アロケーションなしで、高水準のコードを使って生メモリを処理できます。

let buf = UnsafeMutableBufferPointer<Int>.allocate(capacity: 4)
_ = buf.initialize(from: [1, 2, 3, 4])
let total = buf.reduce(0, +)   // 10
let evens = buf.filter { $0 % 2 == 0 } // [2, 4]
print(total, evens)

可変バッファを確保する

UnsafeMutableBufferPointer<T>.allocate(capacity:)はメモリブロックを確保し、バッファを1ステップで返します。読み取る前にinitializeし、処理が終わったらdeallocateする必要があります。

let buf = UnsafeMutableBufferPointer<Int>.allocate(capacity: 5)
buf.initialize(repeating: 0)
defer {
    buf.deinitialize()
    buf.deallocate()
}

境界ではチェックされない

0..<countの範囲内でサブスクリプトを使うのは問題ありません。ただし、バッファは指定したcountが正しいかどうかを確認できません。実際の確保領域より大きなcountでバッファを作成すると、読み取りが領域の末尾を越えて未定義のメモリに及びます。

countは、必ず作成した同じメモリ確保領域から導出してください。

let p = UnsafeMutablePointer<Int>.allocate(capacity: 3)
p.initialize(repeating: 0, count: 3)
// CORRECT: count matches capacity
let good = UnsafeBufferPointer(start: p, count: 3)
// WRONG: count: 10 would read past the block
print(good.count)

空のバッファ

count: 0のバッファも有効で、nilのベースアドレスを持つ場合があります。baseAddressを強制アンラップするのではなく、常にisEmptyを確認するか、ループが単に実行されないことを利用してください。

let empty = UnsafeBufferPointer<Int>(start: nil, count: 0)
print(empty.isEmpty)       // true
print(empty.count)         // 0
for _ in empty { print("never runs") }

ArrayでのwithUnsafeBufferPointer

バッファを取得する最も安全な方法は、既存のArrayに対してwithUnsafeBufferPointerを使うことです。Swiftはストレージが連続しており、クロージャの実行中は有効であることを保証するため、手動でメモリを確保する必要がありません。

let numbers = [4, 8, 15, 16, 23]
let maxValue = numbers.withUnsafeBufferPointer { buf -> Int in
    var m = buf[0]
    for v in buf where v > m { m = v }
    return m
}
print(maxValue) // 23

Arrayのストレージを変更する

withUnsafeMutableBufferPointerを使うと、配列のストレージに直接書き込めます。これは、ARCや境界チェックによるオーバーヘッドを抑えたい、パフォーマンスが重要なインプレース変換で使用されることがあります。

var data = [1, 2, 3, 4]
data.withUnsafeMutableBufferPointer { buf in
    for i in buf.indices { buf[i] *= 10 }
}
print(data) // [10, 20, 30, 40]

C APIでのbaseAddress

ポインタと長さを受け取るC関数を呼び出す場合は、buffer.baseAddressとbuffer.countを渡します。ベースアドレスがnilになるのは空のバッファの場合だけなので、そのケースに対応してください。

let bytes: [UInt8] = [0xDE, 0xAD, 0xBE, 0xEF]
bytes.withUnsafeBufferPointer { buf in
    if let base = buf.baseAddress {
        // c_function(base, buf.count)
        print("start:", base, "len:", buf.count)
    }
}

バッファポインタを使う場面

メモリ領域に対して、連続した型安全なコレクション形式のアクセスが必要な場合にバッファポインタを使用します。たとえば、C配列との橋渡し、バイナリ形式の解析、ホットループでのオーバーヘッド削減などです。可能な限り、手動でメモリを確保するよりもwith...クロージャ形式を優先してください。

// Counting set bits across a byte buffer
let data: [UInt8] = [0b1011, 0b0110, 0b1111]
let bits = data.withUnsafeBufferPointer { buf in
    buf.reduce(0) { $0 + $1.nonzeroBitCount }
}
print(bits) // 3 + 2 + 4 = 9

クイックチェック

単一のポインタに対して、バッファポインタが追加するものを思い出してください。

まとめ

バッファポインタは、連続したメモリ領域を範囲付きのコレクションとしてラップします。

  • UnsafeBufferPointer / UnsafeMutableBufferPointer = ベースアドレス + count
  • 生メモリに対してmap、filter、reduce、forによる反復処理を利用できます。
  • Swiftに有効性とライフタイムを管理させるため、配列ではwithUnsafeBufferPointerを優先してください。
  • 指定したcountはそのまま信頼されるため、実際のメモリ確保領域から導出してください。

よくある質問

「Unsafeバッファポインタ」レッスンは無料ですか?

はい。「Unsafeバッファポインタ」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Swift Academyコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Swift Academyコースには全4レッスンが含まれています。

「Unsafeバッファポインタ」で何を学びますか?

連続したメモリを効率的に処理します。 ブラウザで直接実行するハンズオンコードでSwift Academyを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Swift Academyを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのSwift Academyは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/4です。

「Unsafeバッファポインタ」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このSwift Academyレッスンでコードを書いて実行できますか?

はい。すべてのSwift Academyレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. MemoryLayoutとアライメント
  2. UnsafePointerとUnsafeMutablePointer
  3. Unsafeバッファポインタ
  4. withUnsafeBytesとC相互運用
← Swift Academyに戻る