指針
Swift中指針分為兩類:
-
typed pointer
:指定數據類型指針,UnsafePointer<T>
,T表示泛型
。 -
raw pointer
:未指定數據類型指針(原生指針),UnsafeRawPointer
Swift
中的指針和 OC
中指針的對應關系如下:
-
Raw Pointer的使用
注意
: 指針的內存管理
需要手動
管理,使用完后需要手動釋放
。
例:使用Raw Pointer
在內存中連續
存儲4個整型數據
// 以8字節對齊,分配32字節大小的內存空間
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
// 存儲值到內存
for i in 0..<4 {
// 通過advanced(by:)指定存儲時的步長
p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
}
// 從內存中讀取值
for i in 0..<4 {
// 通過內存平移來讀取內存中的值
let value = p.load(fromByteOffset:i * 8, as: Int.self)
print("index\(i), value:\(value)")
}
// 指針的內存管理需要手動管理
p.deallocate()
-
Type Pointer的使用
withunsafePointer(to:)
首先了解Swift
中$0
、$1
的含義:
Swift
自動為閉包
提供參數名縮寫
功能,可以直接通過$0
和 $1
來表示閉包中的第一個、第二個參數,并且對應的參數類型會根據函數類型來進行判斷。
在前面,我們獲取一個變量本身的地址
都是通過 withUnsafePointer(to:)
的方式獲取。
其函數定義如下:
@inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
可看到:
- 第一個參數是通過
inout
修飾的泛型,前面說過inout
用來修飾的參數表示傳遞的是地址
- 第二個參數是一個
閉包
表達式,通過rethrows
重新拋給Result
(即閉包表達式產生的結果),所以可以通過簡寫
閉包的參數
和返回值
,簡寫如下:
var age : Int = 10
// 第一種:單一表達式:相當于將ptr賦值給p,p的類型UnsafePointer<Int>
let p = withUnsafePointer(to: &age) { ptr in
return ptr
}
// 第二種:此時p為print($0)的返回結果,類型為print方法的返回類型Void
let p = withUnsafePointer(to: &age) { print($0) }
// 第三種:$0賦值給p,p的類型UnsafePointer<Int>
let p = withUnsafePointer(to: &age) { $0 }
從上可知:withUnsafePointer
這個函數result
類型取決于其參數閉包
表達式的返回值
,第一種和第三種得到的 p
都是UnsafePointer<Int>
類型,可以通過 p.pointee
獲取 age
的值。
還可以使用 withUnsafePointer
這個函數修改 age
的值:
age = withUnsafePointer(to: &age, { ptr in
return ptr.pointee + 12
})
-
withUnsafeMutablePointer(to:)
如果想在閉包表達式中直接修改age的值則需要用到withUnsafeMutablePointer
:
withUnsafeMutablePointer(to: &age, { ptr in
ptr.pointee = 122
})
執行 上面代碼,age
的值就直接被修改為 122
了。
UnsafeMutablePointer
// 分配容量為1的內存空間(Int8字節)
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
// 初始化
ptr.initialize(to: 22)
// 手動管理內存:對應initialize
ptr.deinitialize(count: 1)
// 手動管理內存:對應allocate
ptr.deallocate()
這時 ptr.pointee
讀出來則為22。
指針實例演示
實例1:訪問結構體指針
struct YYTeacher {
var age = 12
var name = "YY"
}
var t = YYTeacher()
// 分配裝2個struct的內存空間
let ptr = UnsafeMutablePointer<YYTeacher>.allocate(capacity: 2)
// 初始化第一個struct的內存空間
ptr.initialize(to: YYTeacher())
// 第一種方式:初始化第二個struct的內存空間
ptr.successor().initialize(to: YYTeacher(age: 22, name: "EE"))
// 第二種方式:初始化第二個struct的內存空間
//(ptr + 1).initialize(to: YYTeacher(age: 22, name: "EE"))
// 第三種方式:初始化第二個struct的內存空間
//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)
// 手動管理內存:對應initialize
ptr.deinitialize(count: 2)
// 手動管理內存:對應allocate
ptr.deallocate()
通過查看源碼可知:successor()
本質就是 advanced(by:1)
對比上面 Raw Pointer
例子中的p.advanced(by: i * 8)
,因為上面的 p
是未知類型
,要存儲Int
類型的值就必須每次指定移動 8
的倍數,這里的8是一個Int類型所占的字節數
;而在 UnsafeMutablePointer
中初始化第二個struct內存空間時使用 ptr.advanced(by:1)
,這里已知 ptr
是結構體指針
,只需要告知往前移動幾步
就可以了,這里的 1
相當于往前移動 1個struct步長
(這里的 struct
步長為24
)就好,兩個 struct
相差的字節大小為1 * 24
。
實例2:將實例對象綁定到結構體內存中
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結構體,返回值類型為 UnsafeMutablePointer<HeapObject>
let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
// 訪問內存中的值
print(heapObject.pointee)
-
Unmanaged
取代手動管理內存,用來托管指針
,可以指定內存管理,類似于OC
和CF
交互時的CoreFoundation
(__brige
,有所有權
的轉換),在這里將實例對象t
聲明成了非托管
對象給到ptr
-
passRetained
:增加
引用計數,需要獲取所有權,如OC中的create、copy關鍵字在聲明過程中就需要所有權 -
passUnretained
:不增加
引用計數,不需要獲取其所有權,不再需要手動去釋放如CF
中的CFRelese
,在這里只需要獲得其指針,不需要獲取其所有權 -
bindMemory
:
如果未
綁定,則綁定
如果已
綁定,則重定向
到指定類型內存 - 如果這里將
var kind: Int
改成var kind: UnsafeRawPointer
,在print(heapObject.pointee.kind)
時打印的則是地址
擴展:如何將 heapObject
的屬性 kind
綁定到 Swift
類結構的結構體
內存中?(此時的 kind
是 UnsafeRawPointer
類型)
- 首先定義個
swift
類結構的結構體:
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
這個結構體的內存中
// 綁定內存
let metaptr = heapObject.pointee.kind.bindMemory(to: swift_class.self, capacity: 1)
// 訪問內存中的值
print(metaptr.pointee)
運行結果如下圖:
其本質是因為 metaptr
和 swift_class
的內存結構是一樣
的。
實例3:元祖指針類型轉換
如何將元祖 tul
的指針傳給 testPointer
函數?
var tul = (10,20)
func testPointer(_ p: UnsafePointer<(Int)>) {
print(p)
}
在上面例子中,testPointer
函數的參數是一個UnsafePointer<(Int)>
類型的,而 tul
的指針類型為 UnsafePointer<(Int, Int)>
,這時就需要將 tul
的指針類型重新綁定內存。如下:
withUnsafePointer(to: &tul) {(tulPtr : UnsafePointer<(Int, Int)>) in
// 這里不能使用bindMemory,因為tulPtr已經綁定到具體類型了
// assumingMemoryBound:假定內存綁定,告訴編譯器tulPtr已經綁定過Int類型了,不需要再檢查memory綁定
testPointer(UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
}
那么:bindMemory(to:capacity:)
和assumingMemoryBound(to:)
以及withMemoryRebound(to:capacity:_:)
用法有什么區別呢?
bindMemory(to:capacity:)
:如果以前未綁定
,調用后則首次
綁定到指定類型
的內存;如果以前已綁定,調用后則重新綁定到指定類型(這里編譯器會檢查
之前綁定的類型和現在指定的類型是否布局兼容
)。assumingMemoryBound(to:)
:假定內存綁定
,重新綁定到指定類型的內存,告訴編譯器不用檢查類型(繞過
編譯器檢查類型)withMemoryRebound(to:capacity:_:)
:臨時
更改內存綁定類型。適用于已初始化
的類型指針;使用此方法時,重新綁定的類型要與之前的指針類型具有相同 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
類型的內存,使用結束后,其類型重新綁定成 Int
類型的指針。