Swift
中的指針分為兩類:
① typed pointer 指定數(shù)據(jù)類型指針
,即 UnsafePointer<T>
,其中T
表示泛型
;
②raw pointer 未指定數(shù)據(jù)類型的指針(原生指針
) ,即UnsafeRawPointer
。
與OC的指針
對比如下:
Swift | OC | 說明 |
---|---|---|
unsafePointer<T> | const T * | 指針及所指向的內(nèi)容都不可變 |
unsafeMutablePointer | T * | 指針及其所指向的內(nèi)存內(nèi)容均可變 |
unsafeRawPointer | const void * | 指針指向未知類型,指向的值必須是常量 |
unsafeMutableRawPointer | void * | 指針指向未知類型可變 |
其中,T表示泛型
,對照上文,OC指針
表示,如:NSObjct *objc = [NSObjct alloc] init]
,void *objc = [NSObjct alloc] init]
。
一、type pointer
獲取基本數(shù)據(jù)類型的地址可通過withUnsafePointer(to:)
方法獲取,例如:
/**
value(T):
body(Result): 閉包表達式,通過rethrows重新拋出Result(即閉包表達式產(chǎn)生的結(jié)果)。
方法:
@inlinable public func withUnsafePointer<T, Result>
(to value: T, _ body: (Swift.UnsafePointer<T>) throws -> Result)
rethrows -> Result {}
*/
var age = 18
let p = withUnsafePointer(to: &age) { ptr in
return ptr }
print(p)
//withUnsafePointer: 方法中的閉包屬于單一表達式,因此可以省略參數(shù)、返回值,
//直接使用$0,$0等價于ptr,表示第一個參數(shù),$1表示第二個參數(shù)
//簡寫為: withUnsafePointer(to: &age){print($0)}
//訪問指針的值,可以直接通過:pointee屬性
print(p.pointee)
p
的類型是 UnsafePointer<Int>
:
1.1 修改指針值
修改指針指向的值
,有2種方式,間接修改
& 直接修改
。
var age = 18
//1、間接修改
age = withUnsafePointer(to: &age) { p in
//返回Int整型值
return p.pointee + 10
}
print("1、間接修改:\(age)")
//2.1、直接修改
withUnsafeMutablePointer(to: &age) { p in
p.pointee += 10
}
print("2.1、直接修改:\(age)")
/**
2.2 直接修改,通過 allocate 創(chuàng)建 UnsafeMutablePointer
①,initialize 與 deinitialize是成對的
②,deinitialize中的count與申請時的capacity需要一致
③,需要deallocate
*/
//分配容量大小,為8字節(jié)
let p = UnsafeMutablePointer<Int>.allocate(capacity: 1)
//初始化
p.initialize(to: age)
p.deinitialize(count: 1)
p.pointee += 10
print("2.2、直接修改 p:\(p.pointee)")
print("2.2、直接修改 age:\(age)")
//釋放
p.deallocate()
控制臺打印:
1、間接修改:28
2.1、直接修改:38
2.2、直接修改 p:48
2.2、直接修改 age:38
問題:為什么最后兩次打印的值不一樣,因為指針修改之后,未賦值給age。
二、raw pointer
raw pointer
也叫做原生指針
,就是指未指定數(shù)據(jù)類型的指針。需要注意,raw pointer
需要手動管理
指針的內(nèi)存
,所以指針在使用完需要手動釋放
。
//定義一個未知類型的指針:本質(zhì)是分配32字節(jié)大小的空間,指定對齊方式是8字節(jié)對齊
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
/**
存值1:讀取數(shù)據(jù)時有問題,原因是因為讀取時指定了每次讀取的大小。
但是存儲是直接在8字節(jié)的p中存儲了i+1,即可以理解為并沒有指定存儲時的內(nèi)存大小
*/
//for i in 0..<4 {
// p.storeBytes(of: i + 1, as: Int.self)
//}
//存值2
for i in 0..<4 {
//指定當前移動的步數(shù),即i * 8
p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
}
//取值
for i in 0..<4 {
//p是當前內(nèi)存的首地址,通過內(nèi)存平移來獲取值
let value = p.load(fromByteOffset: i * 8, as: Int.self)
print("index: \(i), value: \(value)")
}
//使用完需要手動釋放
p.deallocate()
三、指針應用
3.1 訪問結(jié)構體
struct Animal{
var age: Int = 8
var height:Float = 1.75
}
然后,使用UnsafeMutablePointer
創(chuàng)建指針,訪問結(jié)構體對象t
// 分配2個Animal大小的空間
let p = UnsafeMutablePointer<Animal>.allocate(capacity: 2)
// 初始化第一個空間
p.initialize(to: Animal())
// 移動,初始化第2個空間
p.successor().initialize(to: Animal(age: 10, height: 1.55))
//異常:p.advanced(by: MemoryLayout<Animal>.stride).initialize(to: Animal(age: 20, height: 1.80))
//正常:p.advanced(by: 1).initialize(to: Animal(age: 20, height: 1.80))
//訪問指針
//方式1:下標訪問
print(p[0])
print(p[1])
//方式2:內(nèi)存平移
print(p.pointee)
print((p+1).pointee)
//方式3:successor()
print(p.pointee)
print(p.successor().pointee) //successor 往前移動
//必須和分配是一致的
p.deinitialize(count: 2)
//釋放
p.deallocate()
注意:通過
advanced(by: MemoryLayout<Animal>.stride)
賦值不行,但是advanced(by: 1)
可以,why?
advanced(by: MemoryLayout<Animal>.stride)
的移動步長是類Animal
實例的大小16字節(jié)
,而advanced(by: 1)
是移動步長為1
。
關鍵 在于這句代碼let ptr = UnsafeMutablePointer<Animal>.allocate(capacity: 2)
,此時我們是知道ptr的具體類型
的,就是指向Animal的指針
。
所以:在確定指針的類型后,通過步長的移動+1,就表示移動了那個類的實例大小空間+1。
3.2 實例對象綁定到struct內(nèi)存
struct HeapObject {
var kind: Int //測試:Int, UnsafeRawPointer
var strongRef: UInt32
var unownedRef: UInt32
}
class Animal{
var age = 18
}
var t = Animal()
將 Animal實例
對象t綁定到結(jié)構體HeapObject
中:
//將t綁定到結(jié)構體內(nèi)存中
/*
1、獲取實例變量的內(nèi)存地址,聲明成了非托管對象
通過 Unmanaged 指定內(nèi)存管理,類似于 OC 與 CF 的交互方式(所有權的轉(zhuǎn)換 __bridge)
- passUnretained 不增加引用計數(shù),即不需要獲取所有權
- passRetained 增加引用計數(shù),即需要獲取所有權
- toOpaque 不透明的指針
*/
let p = Unmanaged.passUnretained(t as AnyObject).toOpaque()
/*
2、綁定到結(jié)構體內(nèi)存,返回值是 UnsafeMutablePointer<T>
- bindMemory 更改當前 UnsafeMutableRawPointer 的指針類型,綁定到具體的類型值
- 如果沒有綁定,則綁定
- 如果已經(jīng)綁定,則重定向到 HeapObject類型上
*/
let heapObject = p.bindMemory(to: HeapObject.self, capacity: 1)
//3、訪問成員變量
//print(heapObject.pointee.kind)
print(heapObject.pointee)
這時打印的kind是數(shù)值
,把Kind的類型
改為 UnsafeRawPointer
,就可以打印地址
了。
3.2.1 綁定到類結(jié)構
Swift底層
對應的class結(jié)構
:
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 flinstanceAlignMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressOffset: UInt32
var description: UnsafeRawPointer
}
然后,把kind
綁定到類結(jié)構
:
//1、綁定到swift_class
let metaPtr = heapObject.pointee.kind.bindMemory(to: swift_class.self, capacity: 1)
//2、訪問
print(metaPtr.pointee)
運行結(jié)果:
HeapObject(kind: 0x0000000100008190, strongRef: 3, unownedRef: 0)
swift_class(kind: 0x0000000100008158, superClass: 0x00000001f49631a8, cachedata1: 0x00000001893f0e60, cachedata2: 0x0000802000000000, data: 0x00000001005169c2, flags: 2, instanceAddressOffset: 0, instanceSize: 24, flinstanceAlignMask: 7, reserved: 0, classSize: 136, classAddressOffset: 16, description: 0x0000000100003c90)
3.3 元組指針類型轉(zhuǎn)換
代碼:
var tul = (10, 20)
//UnsafePointer<T>
func testPointer(_ p : UnsafePointer<Int>){
print(p)
print("第一個值:\(p.pointee),第二個值:\((p+1).pointee)")
}
withUnsafePointer(to: &tul) { (tulPtr: UnsafePointer<(Int, Int)>) in
//不能使用bindMemory,因為已經(jīng)綁定到具體的內(nèi)存中了
//使用assumingMemoryBound,假定內(nèi)存綁定,目的是告訴編譯器ptr已經(jīng)綁定過Int類型了,不需要再檢查memory綁定
testPointer(UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
}
上面是將元組tul
的指針類型 UnsafePointer<(Int, Int)>)
轉(zhuǎn)換成了UnsafePointer<Int>
。
也可以直接告訴編譯器轉(zhuǎn)換成具體的類型:
func testPointer(_ p: UnsafeRawPointer){
p.assumingMemoryBound(to: Int.self)
}
3.4 獲取結(jié)構體的成員變量的指針
struct HeapObject {
var strongRef: UInt32 = 10
var unownedRef: UInt32 = 20
}
func testPointer(_ p: UnsafePointer<Int>){
print(p)
}
//實例化
var t = HeapObject()
//獲取結(jié)構體屬性的指針傳入函數(shù)
withUnsafePointer(to: &t) { (ptr: UnsafePointer<HeapObject>) in
//獲取變量
let strongRef = UnsafeRawPointer(ptr) + MemoryLayout<HeapObject>.offset(of: \HeapObject.strongRef)!
//傳遞strongRef屬性的值
testPointer(strongRef.assumingMemoryBound(to: Int.self))
}
通過withUnsafePointer
將t
綁定到結(jié)構體HeapObject
內(nèi)存中,然后通過 MemoryLayout<HeapObject>.offset()
內(nèi)存平移獲取結(jié)構體成員變量strongRef
,最后通過assumingMemoryBound
進行內(nèi)存的綁定。
assumingMemoryBound
:假定內(nèi)存綁定,就是告訴編譯器,我的類型就是這個,不用檢查了,但實際類型不變。
3.5 臨時綁定內(nèi)存類型
var age = 10
func testPointer(_ p: UnsafePointer<Int64>){
print(p)
}
//異常:參考
withUnsafePointer(to: &age) { (ptr: UnsafePointer<Int>) in
//Cannot convert value of type 'UnsafePointer<Int>' to expected argument type 'UnsafePointer<Int64>'
//testPointer(ptr) //報錯:指針類型不一致
}
let ptr = withUnsafePointer(to: &age) {$0}
//通過withMemoryRebound臨時綁定內(nèi)存類型
ptr.withMemoryRebound(to: Int64.self, capacity: 1) { (ptr: UnsafePointer<Int64>) in
testPointer(ptr)
}
總結(jié)
withMemoryRebound
:臨時更改內(nèi)存綁定類型;
bindMemory(to: Capacity:)
:更改內(nèi)存綁定的類型,如果之前沒有綁定,那么就是首次綁定,如果綁定過了,會被重新綁定為該類型;
assumingMemoryBound
:假定內(nèi)存綁定,就是告訴編譯器,我的類型就是這個,不用檢查了,其實際類型不變。