Swift 中的指針使用
本文轉(zhuǎn)載自OneV's Den的blog
Apple 期望在 Swift 中指針能夠盡量減少登場幾率,因此在 Swift 中指針被映射為了一個泛型類型抖所,并且還比較抽象减噪。這在一定程度上造成了在 Swift 中指針使用的困難桨吊,特別是對那些并不熟悉指針,也沒有多少指針操作經(jīng)驗的開發(fā)者 (包括我自己也是) 來說谁鳍,在 Swift 中使用指針確實是一個挑戰(zhàn)沼死。在這篇文章里,我希望能從最基本的使用開始呕童,總結(jié)一下在 Swift 中使用指針的一些常見方式和場景漆际。這篇文章假定你至少知道指針是什么,如果對指針本身的概念不太清楚的話夺饲,可以先看看這篇五分鐘 C 指針教程 (或者它的中文版本)奸汇,應該會很有幫助施符。
初步
在 Swift 中,指針都使用一個特殊的類型來表示擂找,那就是 UnsafePointer<T>
操刀。遵循了 Cocoa 的一貫不可變原則,UnsafePointer<T>也是不可變的婴洼。當然對應地,它還有一個可變變體UnsafeMutablePointer<T>
撼嗓。絕大部分時間里柬采,C 中的指針都會被以這兩種類型引入到 Swift 中:C 中 const 修飾的指針對應 UnsafePointer(最常見的應該就是 C 字符串的 const char 了),而其他可變的指針則對應 UnsafeMutablePointer*且警。除此之外粉捻,Swift 中存在表示一組連續(xù)數(shù)據(jù)指針的 UnsafeBufferPointer<T>,表示非完整結(jié)構的不透明指針 COpaquePointer等等斑芜。另外你可能已經(jīng)注意到了肩刃,能夠確定指向內(nèi)容的指針類型都是泛型的 struct,我們可以通過這個泛型來對指針指向的類型進行約束以提供一定安全性杏头。
對于一個 UnsafePointer<T>類型盈包,我們可以通過 memory屬性對其進行取值,如果這個指針是可變的 UnsafeMutablePointer<T>類型醇王,我們還可以通過 memory對它進行賦值呢燥。比如我們想要寫一個利用指針直接操作內(nèi)存的計數(shù)器的話,可以這么做:
func incrementor(ptr: UnsafeMutablePointer<Int>) {
ptr.memory += 1
}
var a = 10
incrementor(&a)
a // 11
這里和 C 的指針使用類似寓娩,我們通過在變量名前面加上 &符號就可以將指向這個變量的指針傳遞到接受指針作為參數(shù)的方法中去叛氨。在上面的 incrementor中我們通過直接操作 memory 屬性改變了指針指向的內(nèi)容。與這種做法類似的是使用 Swift 的 inout關鍵字棘伴。我們在將變量傳入 inout參數(shù)的函數(shù)時寞埠,同樣也使用 &符號表示地址。不過區(qū)別是在函數(shù)體內(nèi)部我們不需要處理指針類型焊夸,而是可以對參數(shù)直接進行操作仁连。
func incrementor1(inout num: Int) {
num += 1
}
var b = 10
incrementor1(&b)
b // 11
雖然 &在參數(shù)傳遞時表示的意義和 C 中一樣,是某個“變量的地址”阱穗,但是在 Swift 中我們沒有辦法直接通過這個符號獲取一個 UnsafePointer的實例怖糊。需要注意這一點和 C 有所不同:
// 無法編譯
let a = 100
let b = &a
指針初始化和內(nèi)存管理在 Swift 中不能直接取到現(xiàn)有對象的地址,我們還是可以創(chuàng)建新的* UnsafeMutablePointer*對象颇象。與 Swift 中其他對象的自動內(nèi)存管理不同伍伤,對于指針的管理,是需要我們手動進行內(nèi)存的申請和釋放的遣钳。一個 UnsafeMutablePointer的內(nèi)存有三種可能狀態(tài):
-內(nèi)存沒有被分配扰魂,這意味著這是一個 null 指針,或者是之前已經(jīng)釋--放過
-內(nèi)存進行了分配,但是值還沒有被初始化
-內(nèi)存進行了分配劝评,并且值已經(jīng)被初始化
其中只有第三種狀態(tài)下的指針是可以保證正常使用的姐直。UnsafeMutablePointer的初始化方法 (init) 完成的都是從其他類型轉(zhuǎn)換到 UnsafeMutablePointer的工作。我們?nèi)绻胍陆ㄒ粋€指針蒋畜,需要做的是使用 alloc:這個類方法声畏。該方法接受一個 num: Int
作為參數(shù),將向系統(tǒng)申請 num個數(shù)的對應泛型類型的內(nèi)存姻成。下面的代碼申請了一個 Int大小的內(nèi)存插龄,并返回指向這塊內(nèi)存的指針:
var intPtr = UnsafeMutablePointer<Int>.alloc(1)
// "UnsafeMutablePointer(0x7FD3A8E00060)"
接下來應該做的是對這個指針的內(nèi)容進行初始化,我們可以使用 initialize:方法來完成初始化:
intPtr.initialize(10)
// intPtr.memory 為 10
在完成初始化后科展,我們就可以通過 memory來操作指針指向的內(nèi)存值了均牢。在使用之后,我們最好盡快釋放指針指向的內(nèi)容和指針本身才睹。與 initialize:配對使用的 destroy用來銷毀指針指向的對象徘跪,而與 alloc:
對應的 dealloc:用來釋放之前申請的內(nèi)存。它們都應該被配對使用:
intPtr.destroy()
intPtr.dealloc(1)
intPtr = nil
注意其實在這里對于 Int這樣的在 C 中映射為 int 的 “平凡值” 來說琅攘,destroy并不是必要的垮庐,因為這些值被分配在常量段上。但是對于像類的對象或者結(jié)構體實例來說坞琴,如果不保證初始化和摧毀配對的話突硝,是會出現(xiàn)內(nèi)存泄露的。所以沒有特殊考慮的話置济,不論內(nèi)存中到底是什么解恰,保證 initialize:和 destroy配對會是一個好習慣。
指向數(shù)組的指針
在 Swift 中將一個數(shù)組作為參數(shù)傳遞到 C API 時浙于,Swift 已經(jīng)幫助我們完成了轉(zhuǎn)換护盈,這在 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 數(shù)組的 C API,其要求的類型為 UnsafePointer羞酗,而非 const 的數(shù)組則對應 UnsafeMutablePointer
腐宋。使用時,對于 const 的參數(shù)檀轨,我們直接將 Swift 數(shù)組傳入 (上例中的 a和 b)胸竞;而對于可變的數(shù)組,在前面加上 &后傳入即可 (上例中的 result)参萄。
對于傳參卫枝,Swift 進行了簡化,使用起來非常方便讹挎。但是如果我們想要使用指針來像之前用 memory的方式直接操作數(shù)組的話校赤,就需要借助一個特殊的類型:UnsafeMutableBufferPointer吆玖。Buffer Pointer 是一段連續(xù)的內(nèi)存的指針,通常用來表達像是數(shù)組或者字典這樣的集合類型马篮。
var array = [1, 2, 3, 4, 5]
var arrayPtr = UnsafeMutableBufferPointer<Int>(start: &array, count: array.count)
// baseAddress 是第一個元素的指針
var basePtr = arrayPtr.baseAddress as UnsafeMutablePointer<Int>
basePtr.memory // 1
basePtr.memory = 10
basePtr.memory // 10
//下一個元素
var nextPtr = basePtr.successor()
nextPtr.memory // 2
指針操作和轉(zhuǎn)換
withUnsafePointer
上面我們說過沾乘,在 Swift 中不能像 C 里那樣使用 &
符號直接獲取地址來進行操作。如果我們想對某個變量進行指針操作浑测,我們可以借助 withUnsafePointer
這個輔助方法翅阵。這個方法接受兩個參數(shù),第一個是 inout
的任意類型迁央,第二個是一個閉包掷匠。Swift 會將第一個輸入轉(zhuǎn)換為指針,然后將這個轉(zhuǎn)換后的 Unsafe
的指針作為參數(shù)漱贱,去調(diào)用閉包夭委。使用起來大概是這個樣子:
var test = 10test = withUnsafeMutablePointer(&test, { (ptr: UnsafeMutablePointer<Int>) -> Int in ptr.memory += 1 return ptr.memory})test // 11