Swift 指針

前言

本篇文章主要講解一下Swift中的指針秦效,以及相關(guān)的應(yīng)用場景指針也是面試官經(jīng)常問到的知識點(diǎn)恢氯,希望大家能夠掌握带斑。

一鼓寺、指針類別

Swift中的指針分為兩類

  1. typed pointer: 指定數(shù)據(jù)類型指針,即UnsafePointer<T>勋磕,其中T表示泛型
  2. 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種方式??
  1. 通過withUnsafeMutablePointer方法??
var age = 18
withUnsafeMutablePointer(to: &age) { ptr in
    ptr.pointee += 12
}
  1. 通過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)??

  • initializedeinitialize需成對使用
  • deinitialize中的count與申請時的capacity需要一致
  • 使用完后必須deallocate

1.2 raw pointer

raw pointer也叫做原生指針,就是指未指定數(shù)據(jù)類型的指針肝陪。

使用原生指針需要注意2點(diǎn)??

  1. 對于指針的內(nèi)存管理是需要手動管理
  2. 指針在使用完需要手動釋放

例如??

//定義一個未知類型的指針:本質(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)卻可以呢茸苇?

  1. advanced(by: MemoryLayout<LGTeacher>.stride) 的移動步長是類LGTeacher實(shí)例的大小 -->32 = 16(metadata8 + refcount8) + 16(age8+height8),而advanced(by: 1)是移動步長為1沦寂。
  2. 關(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 pointerraw pointer芦岂,然后講解了指針的幾個常見的應(yīng)用場景,包含更改內(nèi)存綁定的類型垫蛆,假定內(nèi)存綁定臨時更改內(nèi)存綁定類型禽最。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市袱饭,隨后出現(xiàn)的幾起案子弛随,更是在濱河造成了極大的恐慌,老刑警劉巖宁赤,帶你破解...
    沈念sama閱讀 222,464評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異栓票,居然都是意外死亡决左,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,033評論 3 399
  • 文/潘曉璐 我一進(jìn)店門走贪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來佛猛,“玉大人,你說我怎么就攤上這事坠狡〖陶遥” “怎么了?”我有些...
    開封第一講書人閱讀 169,078評論 0 362
  • 文/不壞的土叔 我叫張陵逃沿,是天一觀的道長婴渡。 經(jīng)常有香客問我,道長凯亮,這世上最難降的妖魔是什么边臼? 我笑而不...
    開封第一講書人閱讀 59,979評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮假消,結(jié)果婚禮上柠并,老公的妹妹穿的比我還像新娘。我一直安慰自己富拗,他們只是感情好臼予,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,001評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著啃沪,像睡著了一般粘拾。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上创千,一...
    開封第一講書人閱讀 52,584評論 1 312
  • 那天半哟,我揣著相機(jī)與錄音酬滤,去河邊找鬼。 笑死寓涨,一個胖子當(dāng)著我的面吹牛盯串,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播戒良,決...
    沈念sama閱讀 41,085評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼体捏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了糯崎?” 一聲冷哼從身側(cè)響起几缭,我...
    開封第一講書人閱讀 40,023評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎沃呢,沒想到半個月后年栓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,555評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡薄霜,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,626評論 3 342
  • 正文 我和宋清朗相戀三年某抓,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片惰瓜。...
    茶點(diǎn)故事閱讀 40,769評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡否副,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出崎坊,到底是詐尸還是另有隱情备禀,我是刑警寧澤,帶...
    沈念sama閱讀 36,439評論 5 351
  • 正文 年R本政府宣布奈揍,位于F島的核電站曲尸,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏男翰。R本人自食惡果不足惜队腐,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,115評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望奏篙。 院中可真熱鬧柴淘,春花似錦、人聲如沸秘通。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,601評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽肺稀。三九已至第股,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間话原,已是汗流浹背夕吻。 一陣腳步聲響...
    開封第一講書人閱讀 33,702評論 1 274
  • 我被黑心中介騙來泰國打工诲锹, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人涉馅。 一個月前我還...
    沈念sama閱讀 49,191評論 3 378
  • 正文 我出身青樓归园,卻偏偏與公主長得像,于是被迫代替她去往敵國和親稚矿。 傳聞我的和親對象是個殘疾皇子庸诱,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,781評論 2 361

推薦閱讀更多精彩內(nèi)容