Swiftのポインタ使用
原文作者:王巍(onevcat)
テキストリンク:
https://onevcat.com/2015/01/s...
Appleは,Swiftにおいてポインタができるだけ登場確率を減らすことを期待しているため,Swiftにおいてポインタは汎用型にマッピングされ,抽象的である.これはある程度Swiftでポインタを使用する困難をもたらし、特にポインタに慣れていないし、ポインタの操作経験もあまりない開発者(私自身も含む)にとって、Swiftでポインタを使用することは確かに挑戦です.この文章では、基本的な使い方から始め、Swiftでポインタを使う一般的な方法やシーンをまとめたいと思います.この文章は少なくともポインタが何なのか知っていると仮定していますが、ポインタ自体の概念がよく分からない場合は、まずこの文章を見てみましょう.
5分Cポインタチュートリアル (またはその
中国語バージョン)、役に立つはずです.
イニシャル
Swiftでは、ポインタは特殊なタイプで表されています.それは
1つについて
ここではCのポインタと同様に,変数名の前に&記号を付けることで,この変数を指すポインタをパラメータとして受け入れる方法に渡すことができる.上記の
このようなやり方と似ているのは、Swiftの
ポインタの初期化とメモリ管理は、Swiftで既存のオブジェクトのアドレスを直接取得することはできません.また、新しいUnsafeMutablePointerオブジェクトを作成することもできます.Swiftの他のオブジェクトの自動メモリ管理とは異なり、ポインタの管理には、メモリの申請と解放を手動で行う必要があります.1つのUnsafeMutablePointerのメモリには、次の3つの可能な状態があります.メモリは割り当てられていません.これはnullポインタであるか、 が以前に解放されたことを意味します.メモリは割り当てられているが、値はまだ初期化されていない メモリは割り当てられ、値は に初期化された.
このうち3つ目の状態のポインタだけが正常に使用できる.
次に、このポインタの内容を初期化し、
初期化が完了すると、ポインタが指すメモリ値を
使用後、ポインタが指す内容とポインタ自体をできるだけ早く解放したほうがいいです.
なお、ここでは、定数セグメントに割り当てられているため、IntのようなCでintにマッピングされる「平凡値」については、deinitializeは必要ではない.しかし、クラスのようなオブジェクトや構造体のインスタンスでは、ペアリングの初期化と破壊が保証されていないと、メモリの漏洩が発生します.だから特に考えなければ、メモリの中に何があるかにかかわらず、initialize:deinitializeとペアを組むのは良い習慣です.
配列へのポインタ
Swiftが1つの配列をパラメータとしてC APIに渡すと、Swiftは変換を完了するのに役立ち、Appleの公式ブログには良い例があります.
一般的なconst配列を受け入れるC APIの場合、その要求されるタイプは
伝参については、Swiftが簡略化されており、とても使いやすいです.しかし、
ポインタ操作と変換withUnsafePointer/withUnsafeMutablePointerでは、SwiftではCのように
ここでは,メソッドの呼び出しによって値をポインタに変換する必要がないという点で,最初の
unsafeBitCast
総括Swiftは設計的には安全を重要な原則としているので、うるさいかもしれませんが、Swiftでポインタを直接使用し操作することを最後の手段とすべきであることを再確認し、安全を確保することはできません.従来のCコードとシームレスに組み合わせたObjective-CコードからSwiftへの移行は小さなプロジェクトではなく、私たちのコードライブラリにはCと協力する場所が時々現れるに違いありません.もちろん、Swiftを使用して古いコードの一部を書き換えることはできますが、セキュリティやパフォーマンスが重要な部分のように、C APIを引き続き使用する以外に選択肢はありません.それらのAPIを引き続き使用したい場合は、基本的なSwiftポインタの操作と使用に関する知識を知ると役立ちます.
新しいコードについては,
テキストリンク:
https://onevcat.com/2015/01/s...
Appleは,Swiftにおいてポインタができるだけ登場確率を減らすことを期待しているため,Swiftにおいてポインタは汎用型にマッピングされ,抽象的である.これはある程度Swiftでポインタを使用する困難をもたらし、特にポインタに慣れていないし、ポインタの操作経験もあまりない開発者(私自身も含む)にとって、Swiftでポインタを使用することは確かに挑戦です.この文章では、基本的な使い方から始め、Swiftでポインタを使う一般的な方法やシーンをまとめたいと思います.この文章は少なくともポインタが何なのか知っていると仮定していますが、ポインタ自体の概念がよく分からない場合は、まずこの文章を見てみましょう.
5分Cポインタチュートリアル (またはその
中国語バージョン)、役に立つはずです.
イニシャル
Swiftでは、ポインタは特殊なタイプで表されています.それは
UnsafePointer
.Cocoaの一貫した不変の原則に従い、UnsafePointer
可変ではありません.もちろん、それに対応して、UnsafeMutablePointer
の可変変異体もあります.ほとんどの時間、Cの中のポインタはこの2つのタイプでSwiftに導入されます:Cの中のconst修飾のポインタは対応します UnsafePointer
(最も一般的なのはC文字列のはずです const char *
)、その他の可変ポインタは UnsafeMutablePointer
.このほか、Swiftには連続データポインタのセットを示す UnsafeBufferPointer
、不完全な構造を表す不透明なポインタ COpaquePointer
などなど.また、コンテンツを指すポインタのタイプがすべて汎用structであることを決定できることに気づいたかもしれません.この汎用型によってポインタが指すタイプを制約して、一定のセキュリティを提供することができます.1つについて
UnsafePointer
タイプ、 pointee
このポインタが可変であれば、属性は値を取ります. UnsafeMutablePointer
タイプは、 pointee
に値を付けます.たとえば、ポインタでメモリを直接操作するカウンタを書きたい場合は、次のようにします.func incrementor(ptr: UnsafeMutablePointer) {
ptr.pointee += 1
}
var a = 10
incrementor(&a)
a // 11
ここではCのポインタと同様に,変数名の前に&記号を付けることで,この変数を指すポインタをパラメータとして受け入れる方法に渡すことができる.上記の
incrementor
では、pointee
のプロパティを直接操作することによって、ポインタが指す内容を変更しました.このようなやり方と似ているのは、Swiftの
inout
キーワードを使用することです.inout
パラメータの関数に変数を渡す場合も、&シンボルを使用してアドレスを表します.ただし、違いは、ポインタタイプを処理する必要がなく、パラメータを直接操作できることです.func incrementor1(inout num: Int) {
num += 1
}
var b = 10
incrementor1(&b)
b // 11
&
はパラメータ伝達時に示す意味はCと同様にある「変数のアドレス」であるが,Swiftではこの記号を直接介してUnsafePointer
のインスタンスを取得することはできない.Cとは異なる点に注意してください.//
let a = 100
let b = &a
ポインタの初期化とメモリ管理は、Swiftで既存のオブジェクトのアドレスを直接取得することはできません.また、新しいUnsafeMutablePointerオブジェクトを作成することもできます.Swiftの他のオブジェクトの自動メモリ管理とは異なり、ポインタの管理には、メモリの申請と解放を手動で行う必要があります.1つのUnsafeMutablePointerのメモリには、次の3つの可能な状態があります.
このうち3つ目の状態のポインタだけが正常に使用できる.
UnsafeMutablePointer
の初期化方法(init)は、他のタイプからUnsafeMutablePointer
に変換された作業を完了する.ポインタを新規作成するには、allocate(capacity:)
という方法を使用する必要があります.この方法は、パラメータ capacity: Int
に従って、capacity
個の数の対応する汎用タイプのメモリをシステムに申請する.次のコードは、Int
サイズのメモリを申請し、このメモリへのポインタを返します.var intPtr = UnsafeMutablePointer.allocate(capacity: 1)
// "UnsafeMutablePointer(0x7FD3A8E00060)"
次に、このポインタの内容を初期化し、
initialize(to:)
の方法で初期化を完了することができます.intPtr.initialize(to: 10)
// intPtr.pointee 10
初期化が完了すると、ポインタが指すメモリ値を
pointee
で操作できます.使用後、ポインタが指す内容とポインタ自体をできるだけ早く解放したほうがいいです.
initialize:
とペアで使用されるdeinitialize:
は、ポインタが指すオブジェクトを破棄するために使用され、allocate(capacity:)
に対応するdeallocate(capacity:)
は、以前に出願されたメモリを解放するために使用される.ペアで使用する必要があります.intPtr.deinitialize()
intPtr.deallocate(capacity: 1)
intPtr = nil
なお、ここでは、定数セグメントに割り当てられているため、IntのようなCでintにマッピングされる「平凡値」については、deinitializeは必要ではない.しかし、クラスのようなオブジェクトや構造体のインスタンスでは、ペアリングの初期化と破壊が保証されていないと、メモリの漏洩が発生します.だから特に考えなければ、メモリの中に何があるかにかかわらず、initialize:deinitializeとペアを組むのは良い習慣です.
配列へのポインタ
Swiftが1つの配列をパラメータとしてC APIに渡すと、Swiftは変換を完了するのに役立ち、Appleの公式ブログには良い例があります.
import Accelerate
let a: [Float] = [1, 2, 3, 4]
let b: [Float] = [0.5, 0.25, 0.125, 0.0625]
var result: [Float] = [0, 0, 0, 0]
vDSP_vadd(a, 1, b, 1, &result, 1, 4)
// result now contains [1.5, 2.25, 3.125, 4.0625]
一般的なconst配列を受け入れるC APIの場合、その要求されるタイプは
UnsafePointer
であり、constの配列ではなくUnsafeMutablePointer
である.使用する場合、constのパラメータについては、Swift配列を直接入力します(前例の a
およびb
).また、可変配列については、前に &
を加えて伝来すればよい(前例のresult
).伝参については、Swiftが簡略化されており、とても使いやすいです.しかし、
pointee
のようにポインタを使用して配列を直接操作するには、UnsafeMutableBufferPointer
という特殊なタイプが必要です.Buffer Pointerは連続したメモリのポインタであり、通常は配列や辞書のような集合タイプを表すために使用されます.var array = [1, 2, 3, 4, 5]
var arrayPtr = UnsafeMutableBufferPointer(start: &array, count: array.count) // baseAddress , UnsafeMutablePointer if let basePtr = arrayPtr.baseAddress {
print(basePtr.pointee) // 1
basePtr.pointee = 10
print(basePtr.pointee) // 10
//
let nextPtr = basePtr.successor()
print(nextPtr.pointee) // 2
}
ポインタ操作と変換withUnsafePointer/withUnsafeMutablePointerでは、SwiftではCのように
&
記号を使って直接アドレスを取得して操作することはできません.ある変数をポインタ操作したい場合は、 withUnsafePointer
またはwithUnsafeMutablePointer
の2つの補助方法を使用することができます.この2つの方法は2つのパラメータを受け入れ,1つ目は inout
の任意のタイプであり,2つ目は閉パケットである.Swiftは最初の入力をポインタに変換し、この変換後のUnsafe
のポインタをパラメータとして閉パケットを呼び出す.withUnsafePointer
またはwithUnsafeMutablePointer
の違いは、前者が変換したポインタが可変ではなく、後者が変換したポインタが可変である.使用すると大体このようになります.var test = 10
test = withUnsafeMutablePointer(to: &test, { (ptr: UnsafeMutablePointer) -> Int in
ptr.pointee += 1
return ptr.pointee
})
test // 11
ここでは,メソッドの呼び出しによって値をポインタに変換する必要がないという点で,最初の
incrementor
と同じことをした.このような利点は、一度しか実行できないポインタ操作にとって明らかであり、「私たちはこのポインタに何かをしたい」という意図をより明確に表現することができます.unsafeBitCast
unsafeBitCast
は非常に危険な操作であり、ポインタが指すメモリを強制的にビット単位でターゲットのタイプに変換します.この変換はSwiftのタイプ管理以外で行われるため、コンパイラは得られるタイプが正しいかどうかを確保することができず、何をしているのかを明確に知る必要があります.例:let arr = NSArray(object: "meow")
let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), to: CFString.self)
str // “meow”
NSArray
は任意のNSObject
オブジェクトを格納することができるので、CFArrayGetValueAtIndex
を使用して値を取得すると、結果はUnsafePointer
になります.String
オブジェクトが格納されていることはよく知られているので、 CFString
に直接強制的に変換することができます. unsafeBitCast
のより一般的な使用シーンについては、異なるタイプのポインタ間で変換されます.ポインタ自体が占有する大きさは一定であるため、ポインタのタイプを変換しても致命的な問題はありません.これは、いくつかのC APIとのコラボレーションでよく見られます.例えば、多くのC APIが要求する入力はvoid *
であり、Swiftに対応する入力はUnsafePointer
である.任意のポインタを次のようにUnsafePointerに変換することができます.var count = 100
let voidPtr = withUnsafePointer(to: &count, { (a: UnsafePointer) -> UnsafePointer in
return unsafeBitCast(a, to: UnsafePointer.self)
})
// voidPtr UnsafePointer。 C void *
// UnsafePointer
let intPtr = unsafeBitCast(voidPtr, to: UnsafePointer.self)
intPtr.pointee //100
総括Swiftは設計的には安全を重要な原則としているので、うるさいかもしれませんが、Swiftでポインタを直接使用し操作することを最後の手段とすべきであることを再確認し、安全を確保することはできません.従来のCコードとシームレスに組み合わせたObjective-CコードからSwiftへの移行は小さなプロジェクトではなく、私たちのコードライブラリにはCと協力する場所が時々現れるに違いありません.もちろん、Swiftを使用して古いコードの一部を書き換えることはできますが、セキュリティやパフォーマンスが重要な部分のように、C APIを引き続き使用する以外に選択肢はありません.それらのAPIを引き続き使用したい場合は、基本的なSwiftポインタの操作と使用に関する知識を知ると役立ちます.
新しいコードについては,
Unsafe
の先頭のタイプをできるだけ避けることで,多くの不要な面倒を避けることができることを意味する.Swiftが開発者に与える最大のメリットは、より先進的なプログラミング思想を使って、より速く、より集中的な開発を行うことです.このような思想を尊重する前提の下でこそ、私たちはこの新しい言語がもたらした様々な優位性をよりよく楽しむことができる.明らかに、このような考えにはUnsafePointer
があちこちで使われていることは含まれていません.)