Swift指針

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>

image.png

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))
}

通過withUnsafePointert綁定到結(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)存綁定,就是告訴編譯器,我的類型就是這個,不用檢查了,其實際類型不變。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,996評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,316評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,406評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,128評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,524評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,759評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,310評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,065評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,249評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,479評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,909評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,140評論 1 290
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,984評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,228評論 2 375

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

  • 一、指針 ?? 1、指針類型 ??Swift中的指針分為兩類:指定數(shù)據(jù)類型的指針(typed pointer);未...
    spyn_n閱讀 913評論 1 4
  • Swift 指針 前言 指針,作為編程中最重要的概念,一直存在于各大語言中,下面我們就來探索一下Swift中的指針...
    just東東閱讀 716評論 0 3
  • 指針不是安全的 1.野指針。指針指向?qū)ο筢尫牛羔樧優(yōu)橐爸羔?2.指針超出內(nèi)存空間邊界訪問。如數(shù)組越界 3.原?指...
    張?zhí)煊頮bba7閱讀 597評論 0 1
  • 前言 本篇文章主要講解一下Swift中的指針,以及相關的應用場景,指針也是面試官經(jīng)常問到的知識點,希望大家能夠掌握...
    深圳_你要的昵稱閱讀 3,461評論 0 11
  • 指針分類: raw pointer:未指定數(shù)據(jù)類型的指針(原生指針) typed pointer:指定數(shù)據(jù)類型的指...
    BBLv閱讀 574評論 0 1