前言
本篇文章主要講解一下Swift中的指針
秦效,以及相關(guān)的應(yīng)用場景
,指針
也是面試官經(jīng)常問到的知識點(diǎn)恢氯,希望大家能夠掌握带斑。
一鼓寺、指針類別
Swift中的指針分為兩類
-
typed pointer
: 指定數(shù)據(jù)類型指針,即UnsafePointer<T>
勋磕,其中T表示泛型
-
raw pointer
: 未指定數(shù)據(jù)類型的指針(原生指針) 侄刽,即UnsafeRawPointer
與OC中的指針的對比??
OC | Swift | 釋義 |
---|---|---|
const T * | unsafePointer<T> | 指針及所指向的內(nèi)容都不可變
|
T * | unsafeMutablePointer | 指針及所指向的內(nèi)容都可變
|
const void * | unsafeRawPointer | 無類型指針,指向的值必須是常量
|
void * | unsafeMutableRawPointer | 無類型指針朋凉,也叫通用指針
|
1.1 type pointer
我們獲取基本數(shù)據(jù)類型
的地址
可通過withUnsafePointer(to:)
方法獲取,例如??
var age = 18
let p = withUnsafePointer(to: &age) { ptr in
return ptr }
print(p)
在Swift源碼中搜索withUnsafePointer(to:)
醋安,查看其定義??
我們以arm64為例杂彭,其定義??
@inlinable public func withUnsafePointer<T, Result>(to value: T, _ body: (Swift.UnsafePointer<T>) throws -> Result) rethrows -> Result {}
第二個參數(shù)傳入的是閉包表達(dá)式
,然后通過rethrows
重新拋出Result
(即閉包表達(dá)式產(chǎn)生的結(jié)果)吓揪。既然是閉包亲怠,那么上面的例子,我們可以簡寫??
withUnsafePointer(to: &age){print($0)}
因為withUnsafePointer
方法中的閉包
屬于單一表達(dá)式
柠辞,因此可以省略參數(shù)团秽、返回值,直接使用$0
叭首,$0
等價于ptr
习勤,表示第一個參數(shù),$1
表示第二個參數(shù)焙格,以此類推图毕。
再看看p
的類型??
類型是UnsafePointer<Int>
。
如何訪問指針指向的值
我們可以通過指針的pointee屬性
訪問變量值
??
var age = 18
let p = withUnsafePointer(to: &age) { ptr in
return ptr }
print(p.pointee)
如何修改指針指向的值
有2種方式眷唉,間接修改
& 直接修改
予颤。
- 間接修改
var age = 18
age = withUnsafePointer(to: &age) { ptr in
//返回Int整型值
return ptr.pointee + 12
}
print(age)
在閉包中直接通過ptr.pointee修改
并返回
。
- 直接修改
直接修改也分2種方式??
- 通過
withUnsafeMutablePointer
方法??
var age = 18
withUnsafeMutablePointer(to: &age) { ptr in
ptr.pointee += 12
}
- 通過
allocate
創(chuàng)建UnsafeMutablePointer
??
var age = 18
//分配容量大小冬阳,為8字節(jié)
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
//初始化
ptr.initialize(to: age)
ptr.deinitialize(count: 1)
ptr.pointee += 12
print(ptr.pointee)
//釋放
ptr.deallocate()
通過
allocate
創(chuàng)建UnsafeMutablePointer
蛤虐,需要注意以下幾點(diǎn)??
initialize
與deinitialize
需成對使用deinitialize
中的count
與申請時的capacity
需要一致
- 使用完后必須
deallocate
1.2 raw pointer
raw pointer也叫做原生指針
,就是指未指定數(shù)據(jù)類型
的指針肝陪。
使用
原生指針
需要注意2點(diǎn)??
- 對于指針的內(nèi)存管理是需要
手動管理
的- 指針在使用完需要
手動釋放
例如??
//定義一個未知類型的指針:本質(zhì)是分配32字節(jié)大小的空間驳庭,指定對齊方式是8字節(jié)對齊
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
//存值
for i in 0..<4 {
p.storeBytes(of: i + 1, as: Int.self)
}
//取值
for i in 0..<4 {
//p是當(dāng)前內(nèi)存的首地址,通過內(nèi)存平移來獲取值
let value = p.load(fromByteOffset: i * 8, as: Int.self)
print("index: \(i), value: \(value)")
}
//使用完需要手動釋放
p.deallocate()
運(yùn)行??
上圖可見见坑,讀取出來的值不對嚷掠,原因是存值for循環(huán)時,p中每8個字節(jié)存儲的是i+1荞驴,但是卻沒有指定i+1這個值占的內(nèi)存大小不皆,也就是沒有指定值的內(nèi)存大小
。
解決:通過advanced(by:)
指定存儲時的步長
熊楼。
for i in 0..<4 {
//指定當(dāng)前移動的步數(shù)霹娄,即i * 8
p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
}
二能犯、相關(guān)應(yīng)用場景
2.1 訪問結(jié)構(gòu)體對象
我們先定義一個結(jié)構(gòu)體??
struct LGTeacher {
var age = 18
var height = 1.85
}
var t = LGTeacher()
然后我們使用UnsafeMutablePointer
創(chuàng)建指針,然后訪問結(jié)構(gòu)體對象t犬耻,代碼??
// 分配2個LGTeacher大小的空間
let ptr = UnsafeMutablePointer<LGTeacher>.allocate(capacity: 2)
// 初始化第一個空間
ptr.initialize(to: LGTeacher())
// 移動踩晶,初始化第2個空間
ptr.successor().initialize(to: LGTeacher(age: 20, height: 1.75))
//訪問方式一 下標(biāo)訪問
print(ptr[0])
print(ptr[1])
//訪問方式二 內(nèi)存平移
print(ptr.pointee)
print((ptr+1).pointee)
//訪問方式三 successor()
print(ptr.pointee)
//successor 往前移動
print(ptr.successor().pointee)
//必須和分配是一致的
ptr.deinitialize(count: 2)
//釋放
ptr.deallocate()
有3種方式訪問 ?? 下標(biāo) + 內(nèi)存平移 + successor()
。運(yùn)行??
那能否通過advanced(by: MemoryLayout)
的方式來訪問呢枕磁?改一下初始化第二個LGPerson的代碼??
ptr.advanced(by: MemoryLayout<LGTeacher>.stride).initialize(to: LGTeacher(age: 20, height: 1.80))
運(yùn)行看看??
第二個LGPerson的數(shù)據(jù)出問題了渡蜻,看來通過advanced(by: MemoryLayout)
指定步長的方式是錯誤的。正確的方式可以有以下幾種??
// 內(nèi)存平移
(ptr + 1).initialize(to: CJLTeacher(age: 20, height: 1.80))
// successor()
ptr.successor().initialize(to: CJLTeacher(age: 20, height: 1.80))
// advanced(by: 1)
ptr.advanced(by: 1).initialize(to: CJLTeacher(age: 20, height: 1.80))
那為什么advanced(by: MemoryLayout<LGTeacher>.stride)
不行计济,但是advanced(by: 1)
卻可以呢茸苇?
-
advanced(by: MemoryLayout<LGTeacher>.stride)
的移動步長是類LGTeacher實(shí)例的大小 -->32 =16(metadata8 + refcount8) + 16(age8+height8)
,而advanced(by: 1)
是移動步長為1
沦寂。 - 關(guān)鍵在于這句代碼-->
let ptr = UnsafeMutablePointer<LGTeacher>.allocate(capacity: 2)
学密,此時我們是知道ptr的具體類型的,就是指向LGTeacher的指針
所以在確定指針的類型后传藏,通過步長的移動+1腻暮,就表示移動了那個類的實(shí)例大小空間+1。
2.2 實(shí)例對象綁定到struct的內(nèi)存
示例代碼??
struct HeapObject {
var kind: Int
var strongRef: UInt32
var unownedRef: UInt32
}
class LGTeacher{
var age = 18
}
var t = LGTeacher()
我們將 LGTeacher實(shí)例對象t綁定到結(jié)構(gòu)體HeapObject中毯侦,代碼??
//將t綁定到結(jié)構(gòu)體內(nèi)存中
//1哭靖、獲取實(shí)例變量的內(nèi)存地址,聲明成了非托管對象
/*
通過Unmanaged指定內(nèi)存管理侈离,類似于OC與CF的交互方式(所有權(quán)的轉(zhuǎn)換 __bridge)
- passUnretained 不增加引用計數(shù)款青,即不需要獲取所有權(quán)
- passRetained 增加引用計數(shù),即需要獲取所有權(quán)
- toOpaque 不透明的指針
*/
let ptr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
//2霍狰、綁定到結(jié)構(gòu)體內(nèi)存,返回值是UnsafeMutablePointer<T>
/*
- bindMemory 更改當(dāng)前 UnsafeMutableRawPointer 的指針類型抡草,綁定到具體的類型值
- 如果沒有綁定,則綁定
- 如果已經(jīng)綁定蔗坯,則重定向到 HeapObject類型上
*/
let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
//3康震、訪問成員變量
print(heapObject.pointee.kind)
print(heapObject.pointee.strongRef)
print(heapObject.pointee.unownedRef)
運(yùn)行??
再將kind的類型改成UnsafeRawPointer??
struct HeapObject {
var kind: UnsafeRawPointer
var strongRef: UInt32
var unownedRef: UInt32
}
運(yùn)行??
kind的輸出就是地址了。
接著我們換一個結(jié)構(gòu)體 --> Swift類Class對應(yīng)的底層的結(jié)構(gòu)體
??
struct lg_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也是一個指針宾濒,我們再將kind綁定到lg_swift_class結(jié)構(gòu)體腿短,代碼??
let metaPtr = heapObject.pointee.kind.bindMemory(to: lg_swift_class.self, capacity: 1)
print(metaPtr.pointee)
運(yùn)行??
2.3 元組指針類型轉(zhuǎn)換
示例??
var tul = (10, 20)
//UnsafePointer<T>
func testPointer(_ p : UnsafePointer<Int>){
print(p)
}
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)
}
2.4 獲取結(jié)構(gòu)體的成員變量的指針
示例??
struct HeapObject {
var strongRef: UInt32 = 10
var unownedRef: UInt32 = 20
}
func testPointer(_ p: UnsafePointer<Int>){
print(p)
}
//實(shí)例化
var t = HeapObject()
//獲取結(jié)構(gòu)體屬性的指針傳入函數(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é)構(gòu)體HeapObject內(nèi)存中钝诚,然后通過 MemoryLayout<HeapObject>.offset()
內(nèi)存平移獲取結(jié)構(gòu)體成員變量strongRef
,最后通過assumingMemoryBound
進(jìn)行內(nèi)存的綁定榄棵。
注意:
assumingMemoryBound
假定內(nèi)存綁定凝颇,這里就是告訴編譯器:我的類型就是這個潘拱,你不要檢查
我了,其實(shí)際類型還是原來的類型
。
運(yùn)行??
2.5 臨時綁定內(nèi)存類型
先看以下示例代碼??
var age = 10
func testPointer(_ p: UnsafePointer<Int64>){
print(p)
}
withUnsafePointer(to: &age) { (ptr: UnsafePointer<Int>) in
testPointer(ptr)
}
會報錯:指針類型不一致拧略!
解決方案:通過withMemoryRebound
臨時綁定內(nèi)存類型 ??
var age = 10
func testPointer(_ p: UnsafePointer<Int64>){
print(p)
}
let ptr = withUnsafePointer(to: &age) {$0}
ptr.withMemoryRebound(to: Int64.self, capacity: 1) { (ptr: UnsafePointer<Int64>) in
testPointer(ptr)
}
總結(jié)
本篇文章主要講解了Swift中的2大指針類型:typed pointer
和raw pointer
芦岂,然后講解了指針的幾個常見的應(yīng)用場景,包含更改內(nèi)存綁定的類型
垫蛆,假定內(nèi)存綁定
和臨時更改內(nèi)存綁定類型
禽最。