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) } }
おわり