指針
Swift中指針分為兩類:
-
typed pointer:指定數(shù)據(jù)類型指針,UnsafePointer<T>,T表示泛型。 -
raw pointer:未指定數(shù)據(jù)類型指針(原生指針),UnsafeRawPointer
Swift 中的指針和 OC 中指針的對應關(guān)系如下:

-
Raw Pointer的使用
注意: 指針的內(nèi)存管理需要手動管理,使用完后需要手動釋放。
例:使用Raw Pointer在內(nèi)存中連續(xù)存儲4個整型數(shù)據(jù)
// 以8字節(jié)對齊,分配32字節(jié)大小的內(nèi)存空間
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
// 存儲值到內(nèi)存
for i in 0..<4 {
// 通過advanced(by:)指定存儲時的步長
p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
}
// 從內(nèi)存中讀取值
for i in 0..<4 {
// 通過內(nèi)存平移來讀取內(nèi)存中的值
let value = p.load(fromByteOffset:i * 8, as: Int.self)
print("index\(i), value:\(value)")
}
// 指針的內(nèi)存管理需要手動管理
p.deallocate()
-
Type Pointer的使用
withunsafePointer(to:)
首先了解Swift中$0、$1的含義:
Swift自動為閉包提供參數(shù)名縮寫功能,可以直接通過$0和 $1 來表示閉包中的第一個、第二個參數(shù),并且對應的參數(shù)類型會根據(jù)函數(shù)類型來進行判斷。
在前面,我們獲取一個變量本身的地址都是通過 withUnsafePointer(to:) 的方式獲取。
其函數(shù)定義如下:
@inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
可看到:
- 第一個參數(shù)是通過
inout修飾的泛型,前面說過inout用來修飾的參數(shù)表示傳遞的是地址 - 第二個參數(shù)是一個
閉包表達式,通過rethrows重新拋給Result(即閉包表達式產(chǎn)生的結(jié)果),所以可以通過簡寫閉包的參數(shù)和返回值,簡寫如下:
var age : Int = 10
// 第一種:單一表達式:相當于將ptr賦值給p,p的類型UnsafePointer<Int>
let p = withUnsafePointer(to: &age) { ptr in
return ptr
}
// 第二種:此時p為print($0)的返回結(jié)果,類型為print方法的返回類型Void
let p = withUnsafePointer(to: &age) { print($0) }
// 第三種:$0賦值給p,p的類型UnsafePointer<Int>
let p = withUnsafePointer(to: &age) { $0 }
從上可知:withUnsafePointer 這個函數(shù)result 類型取決于其參數(shù)閉包表達式的返回值,第一種和第三種得到的 p 都是UnsafePointer<Int>類型,可以通過 p.pointee 獲取 age 的值。
還可以使用 withUnsafePointer 這個函數(shù)修改 age 的值:
age = withUnsafePointer(to: &age, { ptr in
return ptr.pointee + 12
})
-
withUnsafeMutablePointer(to:)
如果想在閉包表達式中直接修改age的值則需要用到withUnsafeMutablePointer:
withUnsafeMutablePointer(to: &age, { ptr in
ptr.pointee = 122
})
執(zhí)行 上面代碼,age 的值就直接被修改為 122 了。
UnsafeMutablePointer
// 分配容量為1的內(nèi)存空間(Int8字節(jié))
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
// 初始化
ptr.initialize(to: 22)
// 手動管理內(nèi)存:對應initialize
ptr.deinitialize(count: 1)
// 手動管理內(nèi)存:對應allocate
ptr.deallocate()
這時 ptr.pointee 讀出來則為22。
指針實例演示
實例1:訪問結(jié)構(gòu)體指針
struct YYTeacher {
var age = 12
var name = "YY"
}
var t = YYTeacher()
// 分配裝2個struct的內(nèi)存空間
let ptr = UnsafeMutablePointer<YYTeacher>.allocate(capacity: 2)
// 初始化第一個struct的內(nèi)存空間
ptr.initialize(to: YYTeacher())
// 第一種方式:初始化第二個struct的內(nèi)存空間
ptr.successor().initialize(to: YYTeacher(age: 22, name: "EE"))
// 第二種方式:初始化第二個struct的內(nèi)存空間
//(ptr + 1).initialize(to: YYTeacher(age: 22, name: "EE"))
// 第三種方式:初始化第二個struct的內(nèi)存空間
//ptr.advanced(by: 1).initialize(to:YYTeacher(age: 22, name: "EE"))
// 兩種方式獲取第一個struct的值
print(ptr[0])
print(ptr.pointee)
// 三種方式獲取第二個struct的值
print(ptr[1])
print((ptr + 1).pointee)
print(ptr.successor().pointee)
print(ptr.advanced(by: 1).pointee)
// 手動管理內(nèi)存:對應initialize
ptr.deinitialize(count: 2)
// 手動管理內(nèi)存:對應allocate
ptr.deallocate()
通過查看源碼可知:successor() 本質(zhì)就是 advanced(by:1)

對比上面 Raw Pointer 例子中的p.advanced(by: i * 8) ,因為上面的 p 是未知類型,要存儲Int類型的值就必須每次指定移動 8 的倍數(shù),這里的8是一個Int類型所占的字節(jié)數(shù);而在 UnsafeMutablePointer 中初始化第二個struct內(nèi)存空間時使用 ptr.advanced(by:1) ,這里已知 ptr 是結(jié)構(gòu)體指針,只需要告知往前移動幾步就可以了,這里的 1 相當于往前移動 1個struct步長 (這里的 struct 步長為24)就好,兩個 struct 相差的字節(jié)大小為1 * 24。
實例2:將實例對象綁定到結(jié)構(gòu)體內(nèi)存中
struct HeapObject {
var kind: Int
var strongRef: UInt32
var unownedRef: UInt32
}
class YYTeacher {
var age = 20
}
var t = YYTeacher()
首先:獲取實例對象的值
其次:將 raw pointer 綁定到具體類型指針
// 獲取實例對象的值,返回值類型為 UnsafeMutableRawPointer
let ptr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
// 將當前Raw Pointer-->ptr重新綁定到HeapObject結(jié)構(gòu)體,返回值類型為 UnsafeMutablePointer<HeapObject>
let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
// 訪問內(nèi)存中的值
print(heapObject.pointee)
-
Unmanaged取代手動管理內(nèi)存,用來托管指針,可以指定內(nèi)存管理,類似于OC和CF交互時的CoreFoundation(__brige,有所有權(quán)的轉(zhuǎn)換),在這里將實例對象t聲明成了非托管對象給到ptr -
passRetained:增加引用計數(shù),需要獲取所有權(quán),如OC中的create、copy關(guān)鍵字在聲明過程中就需要所有權(quán) -
passUnretained:不增加引用計數(shù),不需要獲取其所有權(quán),不再需要手動去釋放如CF中的CFRelese,在這里只需要獲得其指針,不需要獲取其所有權(quán) -
bindMemory:
如果未綁定,則綁定
如果已綁定,則重定向到指定類型內(nèi)存 - 如果這里將
var kind: Int改成var kind: UnsafeRawPointer,在print(heapObject.pointee.kind)時打印的則是地址
擴展:如何將 heapObject 的屬性 kind 綁定到 Swift 類結(jié)構(gòu)的結(jié)構(gòu)體內(nèi)存中?(此時的 kind 是 UnsafeRawPointer 類型)
- 首先定義個
swift類結(jié)構(gòu)的結(jié)構(gòu)體:
struct swift_class {
var kind: UnsafeRawPointer
var superClass: UnsafeRawPointer
var cacheData1: UnsafeRawPointer
var cacheData2: UnsafeRawPointer
var data: UnsafeRawPointer
var flags:UInt32
var instanceAddressOffset:UInt32
var instanceSize:UInt32
var instanceAlignMask:UInt16
var reserved:UInt16
var classSize:UInt32
var classAddressOffset:UInt32
var description: UnsafeRawPointer
}
- 將
kind綁定到swift_class這個結(jié)構(gòu)體的內(nèi)存中
// 綁定內(nèi)存
let metaptr = heapObject.pointee.kind.bindMemory(to: swift_class.self, capacity: 1)
// 訪問內(nèi)存中的值
print(metaptr.pointee)
運行結(jié)果如下圖:

其本質(zhì)是因為 metaptr 和 swift_class 的內(nèi)存結(jié)構(gòu)是一樣的。
實例3:元祖指針類型轉(zhuǎn)換
如何將元祖 tul 的指針傳給 testPointer 函數(shù)?
var tul = (10,20)
func testPointer(_ p: UnsafePointer<(Int)>) {
print(p)
}
在上面例子中,testPointer 函數(shù)的參數(shù)是一個UnsafePointer<(Int)> 類型的,而 tul 的指針類型為 UnsafePointer<(Int, Int)> ,這時就需要將 tul 的指針類型重新綁定內(nèi)存。如下:
withUnsafePointer(to: &tul) {(tulPtr : UnsafePointer<(Int, Int)>) in
// 這里不能使用bindMemory,因為tulPtr已經(jīng)綁定到具體類型了
// assumingMemoryBound:假定內(nèi)存綁定,告訴編譯器tulPtr已經(jīng)綁定過Int類型了,不需要再檢查memory綁定
testPointer(UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
}
那么:bindMemory(to:capacity:)和assumingMemoryBound(to:)以及withMemoryRebound(to:capacity:_:)用法有什么區(qū)別呢?
bindMemory(to:capacity:):如果以前未綁定,調(diào)用后則首次綁定到指定類型的內(nèi)存;如果以前已綁定,調(diào)用后則重新綁定到指定類型(這里編譯器會檢查之前綁定的類型和現(xiàn)在指定的類型是否布局兼容)。assumingMemoryBound(to:):假定內(nèi)存綁定,重新綁定到指定類型的內(nèi)存,告訴編譯器不用檢查類型(繞過編譯器檢查類型)withMemoryRebound(to:capacity:_:):臨時更改內(nèi)存綁定類型。適用于已初始化的類型指針;使用此方法時,重新綁定的類型要與之前的指針類型具有相同 size和stride。
var age = 12
func testPointer(_ p: UnsafePointer<UInt64>) {
print(p)
}
let ptr = withUnsafePointer(to: &age){$0}
ptr.withMemoryRebound(to: UInt64.self, capacity: 1) {
testPointer($0)
}
在這里 ptr 本身是 Int 類型的指針,使用 withMemoryRebound 則臨時將其指針類型綁定到 UInt64 類型的內(nèi)存,使用結(jié)束后,其類型重新綁定成 Int 類型的指針。