Swift 中的指標使用

來源:互聯網
上載者:User

Swift 中的指標使用

Apple 期望在 Swift 中指標能夠盡量減少登場幾率,因此在 Swift 中指標被映射為了一個泛型型別,並且還比較抽象。這在一定程度上造成了在 Swift 中指標使用的困難,特別是對那些並不熟悉指標,也沒有多少指標操作經驗的開發人員 (包括我自己也是) 來說,在 Swift 中使用指標確實是一個挑戰。在這篇文章裡,我希望能從最基本的使用開始,總結一下在 Swift 中使用指標的一些常見方式和情境。這篇文章假定你至少知道指標是什麼,如果對指標本身的概念不太清楚的話,可以先看看這篇五分鐘 C 指標教程 (或者它的中文版本),應該會很有協助。

初步

在 Swift 中,指標都使用一個特殊的類型來表示,那就是 UnsafePointer<T>。遵循了 Cocoa 的一貫不可變原則,UnsafePointer<T> 也是不可變的。當然對應地,它還有一個可變變體,UnsafeMutablePointer<T>。絕大部分時間裡,C 中的指標都會被以這兩種類型引入到 Swift 中:C 中 const 修飾的指標對應 UnsafePointer (最常見的應該就是 C 字串的 const char * 了),而其他可變的指標則對應 UnsafeMutablePointer。除此之外,Swift 中存在表示一組連續資料指標的 UnsafeBufferPointer<T>,表示非完整結構的不透明指標 COpaquePointer 等等。另外你可能已經注意到了,能夠確定指向內容的指標類型都是泛型的 struct,我們可以通過這個泛型來對指標指向的類型進行約束以提供一定安全性。

對於一個 UnsafePointer<T> 類型,我們可以通過 memory 屬性對其進行取值,如果這個指標是可變的 UnsafeMutablePointer<T> 類型,我們還可以通過 memory 對它進行賦值。比如我們想要寫一個利用指標直接操作記憶體的計數器的話,可以這麼做:

 
  1. func incrementor(ptr: UnsafeMutablePointer<Int>) { 
  2.     ptr.memory += 1 
  3.  
  4. var a = 10 
  5. incrementor(&a) 
  6.  
  7. a  // 11 

這裡和 C 的指標使用類似,我們通過在變數名前面加上 & 符號就可以將指向這個變數的指標傳遞到接受指標作為參數的方法中去。在上面的 incrementor 中我們通過直接操作 memory 屬性改變了指標指向的內容。

與這種做法類似的是使用 Swift 的 inout 關鍵字。我們在將變數傳入 inout 參數的函數時,同樣也使用 & 符號表示地址。不過區別是在函數體內部我們不需要處理指標類型,而是可以對參數直接進行操作。

 
  1. func incrementor1(inout num: Int) { 
  2.     num += 1 
  3.  
  4. var b = 10 
  5. incrementor1(&b) 
  6.  
  7. b  // 11 

雖然 & 在參數傳遞時表示的意義和 C 中一樣,是某個“變數的地址”,但是在 Swift 中我們沒有辦法直接通過這個符號擷取一個 UnsafePointer 的執行個體。需要注意這一點和 C 有所不同:

 
  1. // 無法編譯 
  2. let a = 100 
  3. let b = &a 

指標初始化和記憶體管理

在 Swift 中不能直接取到現有對象的地址,我們還是可以建立新的 UnsafeMutablePointer 對象。與 Swift 中其他對象的自動記憶體管理不同,對於指標的管理,是需要我們手動進行記憶體的申請和釋放的。一個 UnsafeMutablePointer 的記憶體有三種可能狀態:

  • 記憶體沒有被分配,這意味著這是一個 null 指標,或者是之前已經釋放過
  • 記憶體進行了分配,但是值還沒有被初始化
  • 記憶體進行了分配,並且值已經被初始化

其中只有第三種狀態下的指標是可以保證正常使用的。UnsafeMutablePointer 的初始化方法 (init) 完成的都是從其他類型轉換到 UnsafeMutablePointer 的工作。我們如果想要建立一個指標,需要做的是使用 alloc: 這個類方法。該方法接受一個 num: Int 作為參數,將向系統申請 num 個數的對應泛型型別的記憶體。下面的代碼申請了一個 Int 大小的記憶體,並返回指向這塊記憶體的指標:

 
  1. var intPtr = UnsafeMutablePointer<Int>.alloc(1) 
  2. // "UnsafeMutablePointer(0x7FD3A8E00060)" 

接下來應該做的是對這個指標的內容進行初始化,我們可以使用 initialize: 方法來完成初始化:

 
  1. intPtr.initialize(10) 
  2. // intPtr.memory 為 10 

在完成初始化後,我們就可以通過 memory 來操作指標指向的記憶體值了。

在使用之後,我們最好儘快釋放指標指向的內容和指標本身。與 initialize: 配對使用的 destroy 用來銷毀指標指向的對象,而與 alloc: 對應的 dealloc: 用來釋放之前申請的記憶體。它們都應該被配對使用:

 
  1. intPtr.destroy() 
  2. intPtr.dealloc(1) 
  3. intPtr = nil 

注意其實在這裡對於 Int 這樣的在 C 中映射為 int 的 “平凡值” 來說,destroy 並不是必要的,因為這些值被分配在常量段上。但是對於像類的對象或者結構體執行個體來說,如果不保證初始化和摧毀配對的話,是會出現記憶體泄露的。所以沒有特殊考慮的話,不論記憶體中到底是什麼,保證 initialize: 和 destroy 配對會是一個好習慣。

指向數組的指標

在 Swift 中將一個數組作為參數傳遞到 C API 時,Swift 已經協助我們完成了轉換,這在 Apple 的官方部落格中有個很好的例子:

 
  1. import Accelerate 
  2.  
  3. let a: [Float] = [1, 2, 3, 4] 
  4. let b: [Float] = [0.5, 0.25, 0.125, 0.0625] 
  5. var result: [Float] = [0, 0, 0, 0] 
  6.  
  7. vDSP_vadd(a, 1, b, 1, &result, 1, 4) 
  8.  
  9. // result now contains [1.5, 2.25, 3.125, 4.0625] 

對於一般的接受 const 數組的 C API,其要求的類型為 UnsafePointer,而非 const 的數組則對應 UnsafeMutablePointer。使用時,對於 const 的參數,我們直接將 Swift 數組傳入 (上例中的 a 和 b);而對於可變的數組,在前面加上 & 後傳入即可 (上例中的 result)。

對於傳參,Swift 進行了簡化,使用起來非常方便。但是如果我們想要使用指標來像之前用 memory 的方式直接運算元組的話,就需要藉助一個特殊的類型:UnsafeMutableBufferPointer。Buffer Pointer 是一段連續的記憶體的指標,通常用來表達像是數組或者字典這樣的集合類型。

 
  1. var array = [1, 2, 3, 4, 5] 
  2. var arrayPtr = UnsafeMutableBufferPointer<Int>(start: &array, count: array.count) 
  3. // baseAddress 是第一個元素的指標 
  4. var basePtr = arrayPtr.baseAddress as UnsafeMutablePointer<Int> 
  5.  
  6. basePtr.memory // 1 
  7. basePtr.memory = 10 
  8. basePtr.memory // 10 
  9.  
  10. //下一個元素 
  11. var nextPtr = basePtr.successor() 
  12. nextPtr.memory // 2 

指標操作和轉換

withUnsafePointer

上面我們說過,在 Swift 中不能像 C 裡那樣使用 & 符號直接擷取地址來進行操作。如果我們想對某個變數進行指標操作,我們可以藉助 withUnsafePointer 這個輔助方法。這個方法接受兩個參數,第一個是 inout 的任意類型,第二個是一個閉包。Swift 會將第一個輸入轉換為指標,然後將這個轉換後的 Unsafe 的指標作為參數,去調用閉包。使用起來大概是這個樣子:

 
  1. var test = 10 
  2. test = withUnsafeMutablePointer(&test, { (ptr: UnsafeMutablePointer<Int>) -> Int in 
  3.     ptr.memory += 1 
  4.     return ptr.memory 
  5. }) 
  6.  
  7. test // 11 

這裡其實我們做了和文章一開始的 incrementor 相同的事情,區別在於不需要通過方法的調用來將值轉換為指標。這麼做的好處對於那些只會執行一次的指標操作來說是顯而易見的,可以將“我們就是想對這個指標做點事兒”這個意圖表達得更加清晰明確。

unsafeBitCast

unsafeBitCast 是非常危險的操作,它會將一個指標指向的記憶體強制按位轉換為目標的類型。因為這種轉換是在 Swift 的類型管理之外進行的,因此編譯器無法確保得到的類型是否確實正確,你必須明確地知道你在做什麼。比如:

 
  1. let arr = NSArray(object: "meow") 
  2. let str = unsafeBitCast(CFArrayGetValueAtIndex(arr, 0), CFString.self) 
  3. str // “meow” 

因為 NSArray 是可以存放任意 NSObject 對象的,當我們在使用 CFArrayGetValueAtIndex 從中取值的時候,得到的結果將是一個 UnsafePointer<Void>。由於我們很明白其中存放的是 String 對象,因此可以直接將其強制轉換為 CFString。

關於 unsafeBitCast 一種更常見的使用情境是不同類型的指標之間進行轉換。因為指標本身所佔用的的大小是一定的,所以指標的類型進行轉換是不會出什麼致命問題的。這在與一些 C API 協作時會很常見。比如有很多 C API 要求的輸入是 void *,對應到 Swift 中為 UnsafePointer<Void>。我們可以通過下面這樣的方式將任意指標轉換為 UnsafePointer。

 
  1. var count = 100 
  2. var voidPtr = withUnsafePointer(&count, { (a: UnsafePointer<Int>) -> UnsafePointer<Void> in 
  3.     return unsafeBitCast(a, UnsafePointer<Void>.self) 
  4. }) 
  5. // voidPtr 是 UnsafePointer<Void>。相當於 C 中的 void * 
  6.  
  7. // 轉換回 UnsafePointer<Int> 
  8. var intPtr = unsafeBitCast(voidPtr, UnsafePointer<Int>.self) 
  9. intPtr.memory //100 

總結

Swift 從設計上來說就是以安全作為重要原則的,雖然可能有些囉嗦,但是還是要重申在 Swift 中直接使用和操作指標應該作為最後的手段,它們始終是無法確保安全的。從傳統的 C 代碼和與之無縫配合的 Objective-C 代碼遷移到 Swift 並不是一件小工程,我們的程式碼程式庫肯定會時不時出現一些和 C 協作的地方。我們當然可以選擇使用 Swift 重寫部分陳舊代碼,但是對於像是安全或者效能至關重要的部分,我們可能除了繼續使用 C API 以外別無選擇。如果我們想要繼續使用那些 API 的話,瞭解一些基本的 Swift 指標操作和使用的知識會很有協助。

對於新的代碼,盡量避免使用 Unsafe 開頭的類型,意味著可以避免很多不必要的麻煩。Swift 給開發人員帶來的最大好處是可以讓我們用更加先進的編程思想,進行更快和更專註的開發。只有在尊重這種思想的前提下,我們才能更好地享受這門新語言帶來的種種優勢。顯然,這種思想是不包括到處使用 UnsafePointer 的 :)

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.