UnsafeMutablePointer のパターンをいろいろテストする

はじめに

  • UnsafeMutablePointer の allocate/deallocate/initialize/move などのメソッドを一通り使用する。
  • また、ポインタの扱いでメモリリークが発生しないようにする。

テストしたコードは GitHub にある。

テストで共通使用する関数・クラス

このコードをベースに色々なパターンをテストする。

import Foundation
import CoreAudio

// オブジェクトのアドレスを保存するマップ
var addressMap = [String: Bool]()

// オブジェクトのアドレス(文字列)を得る
func address(_ object: AnyObject) -> String {
  return "\(Unmanaged.passUnretained(object).toOpaque())"
}

// テストの終了処理
func testFinally() {
  assert(addressMap.count == 0)  // アドレスマップの要素数を確認する(0でなければメモリリークをしている
  print("- OK\n")
}


// モッククラス
class Mock {
  
  // インスタンス生成時にアドレス表示&アドレスをマップに保存する
  init () {
    print("init   : \(address(self))")
    addressMap[address(self)] = true  // Store address.
  }
  
  init(_ value: Int) {
    print("init   : \(address(self))")
    addressMap[address(self)] = true  //  Store address.
    
    v1 = value
  }
  
  // deinit() でアドレスマップからアドレスを削除する
  deinit {
    print("deinit : \(address(self))")
    addressMap.removeValue(forKey: address(self))  //  Remove address.
  }
  
  
  // 自由に設定できる変数
  var v1 = Int(0)
}

テストケース

allocate / deallocate パターン

func testAllocateAndDeallocate() {
  print("# \(#function)")
  
  do {
    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)
    
    // 未初期化のメモリはアクセスする前に、初期化する必要がある
    // initialize せずに以下のコードを実行するとエラーになる
    //
    // p.pointee.v1 = ...
    
    p.deallocate()  // deallocate をコールしないとメモリリークが発生する
  }
  
  testFinally() // メモリリークが発生していないことを確認する
}

initialize / deinitialize パターン

func testInitializeAndDeinitialize() {
  print("# \(#function)")
  
  do {
    // モックインスタンスを作成する
    var mock = Mock()
    mock.v1 = 10
    
    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }

    // インスタンスから UnsafeMutablePointer を initialize する    
    p.initialize(from: &mock, count: 1)
    p.pointee.v1 = 20  // initialize した UnsafeMutablePoitner はアクセス可能になる
    
    assert(address(mock) == address(p.pointee)) // インスタンスと UnsafeMutablePointer は同一アドレスである
    assert(mock.v1 == 20)
    
    p.deinitialize(count: 1)  // initialize した分、 deinitialize をコールしないとメモリリークが発生する
  }
  
  testFinally()
}

initialize / move パターン

func testInitializeAndMove() {
  print("# \(#function)")
  
  do {
    // UnsafeMutablePointer を allocate する
    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }
    
    // インスタンス値から UnsafeMutablePointer を初期化する
    p.initialize(to: Mock())
    p.pointee.v1 = 10
    
    // p を move してインスタンスを取得する
    // move したことにより、 deinitialize しなくても p は deinit される
    let mock = p.move()
    
    // p.deinitialize() は不要

    assert(address(mock) == address(p.pointee))
    assert(mock.v1 == 10)
  }
  
  testFinally()
}

moveInitialize パターン

func testMoveInitialize() {
  print("# \(#function)")
  
  do {
    var mock = Mock()
    mock.v1 = 10
    
    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }
    
    p.moveInitialize(from: &mock, count: 1)
    p.pointee.v1 = 20
    
    // moveInitialize() したので p.deinitialize() は不要

    assert(address(mock) == address(p.pointee))  // Check same address.
    assert(p.pointee.v1 == 20)
  }
  
  testFinally()
}

initialize/ assign パターン

func testInitializeAndAssign() {
  print("# \(#function)")
  
  do {
    var mock = Mock()
    mock.v1 = 10
    
    var mock2 = Mock()
    mock2.v1 = 20
    
    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }
    
    p.initialize(from: &mock, count: 1) // mock1 で初期化する
    p.assign(from: &mock2, count: 1) // mock2 をアサインする
    p.pointee.v1 = 30
    
    // UnsafeMutablePointer は mock2 を指している
    assert(address(mock2) == address(p.pointee))  // Check same address.
    assert(mock2.v1 == 30)
    
    p.deinitialize(count: 1)
  }
  
  testFinally()
}

moveAssign パターン

func testMoveAssign() {
  print("# \(#function)")
  
  do {
    var mock = Mock()
    mock.v1 = 10
    
    var mock2 = Mock()
    mock2.v1 = 20
    
    let p = UnsafeMutablePointer<Mock>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }
    
    p.initialize(from: &mock, count: 1) // mock1 で初期化する
    p.moveAssign(from: &mock2, count: 1) // mock2 を move する
    p.pointee.v1 = 30
    
    // UnsafeMutablePointer は mock2 を指している
    assert(address(mock2) == address(p.pointee))  // Check same address.
    assert(mock2.v1 == 30)

    // move したので p.deinitialize() は不要
  }
  
  testFinally()
}

Array パターン

func testArray() {
  print("# \(#function)")
  
  do {
    var array: [Mock] = [Mock(10), Mock(20), Mock(30)]
    
    let p = UnsafeMutablePointer<[Mock]>.allocate(capacity: 1)
    defer {
      p.deallocate()
    }
    
    // Array から UnsafeMutablePointer を初期化する
    p.moveInitialize(from: &array, count: 1)
    p.pointee[0] = Mock(40)
    p.pointee[1] = Mock(50)
    p.pointee[2] = Mock(60)
    
    assert(array.count == p.pointee.count)
    assert(address(array as AnyObject) == address(p.pointee as AnyObject))
    assert(array[0].v1 == 40)
    assert(array[1].v1 == 50)
    assert(array[2].v1 == 60)
  }
  
  testFinally()
}

AudioBufferList パターン

func testAudioBufferList() {
  print("# \(#function)")
  
  do {
    let abl = AudioBufferList.allocate(maximumBuffers: 1)
    free(abl.unsafeMutablePointer) // AudioBufferList を allocate() したあとは free() が必要
  }
}

各テストケースを実行する

上記のテストをまとめて繰り返し実行し、エラーとメモリリークがないことを確認する

while true {
  autoreleasepool {
    testAllocateAndDeallocate()
    testInitializeAndDeinitialize()
    testInitializeAndMove()
    testMoveInitialize()
    testInitializeAndAssign()
    testMoveAssign()
    testArray()
    testAudioBufferList()
    
    Thread.sleep(forTimeInterval: 1)
  }
}

おわり