Swiftのポインタ使用

8029 ワード

原文作者:王巍(onevcat)
テキストリンク:
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つの可能な状態があります.
  • メモリは割り当てられていません.これはnullポインタであるか、
  • が以前に解放されたことを意味します.
  • メモリは割り当てられているが、値はまだ初期化されていない
  • メモリは割り当てられ、値は
  • に初期化された.
    このうち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 があちこちで使われていることは含まれていません.)