Swift進階-指針

Swift進階-類與結(jié)構(gòu)體
Swift-函數(shù)派發(fā)
Swift進階-屬性
Swift進階-指針
Swift進階-內(nèi)存管理
Swift進階-TargetClassMetadata和TargetStructMetadata數(shù)據(jù)結(jié)構(gòu)源碼分析
Swift進階-Mirror解析
Swift進階-閉包
Swift進階-協(xié)議
Swift進階-泛型
Swift進階-String源碼解析
Swift進階-Array源碼解析

前言

為什么說指針不安全?

  • ?如我們在創(chuàng)建?個對象的時候,是需要在堆分配內(nèi)存空間的。但是這個內(nèi)存空間的聲明周期是有限的,也就意味著如果我們使?指針指向這塊內(nèi)容空間,如果當前內(nèi)存空間的?命周期啊到了(引?計數(shù)為0),那么我們當前的指針是不是就變成了未定義的?為了(野指針)。
  • 我們創(chuàng)建的內(nèi)存空間是有邊界的,?如我們創(chuàng)建?個??為10的數(shù)組,這個時候我們通過指針訪問 到了 index = 11 的位置,這個時候是不是就越界了,訪問了?個未知的內(nèi)存空間。
  • 指針類型與內(nèi)存的值類型不?致,也是不安全的。(Int * 和 Int8 *類型容易造成精度缺失)

1.指針類別

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 無類型指針,指向的值必須是常量(指向的內(nèi)存區(qū)域未定)
void * unsafeMutableRawPointer 無類型指針,指向的內(nèi)存區(qū)域未定,也叫通用指針(指向的內(nèi)存區(qū)域未定)

首先我們了解一下內(nèi)存的字節(jié)對齊、實際大小、步長

struct Teacher {
    var age: Int = 18
    var sex: Bool = true
}
print(MemoryLayout<Int>.alignment)    // 8 字節(jié)對齊
print(MemoryLayout<Teacher>.size)     // 9 實際大小
print(MemoryLayout<Teacher>.stride)   // 16 步長
  • 字節(jié)對齊alignment: cpu的一個64位尋址周期讀出來的是8字節(jié)。

  • 實際大小size: 一個Teacher()占用內(nèi)存的真實大小 size = Int + Bool / 9 = 8 + 1。

  • 步長stride: 比如說我要存儲連續(xù)的Teacher實例,從當前Teacher()的起始位置到下一個Teacher()的起始位置,就是一個Teacher實例的步長。這里輸出16是因為存儲一個Teacher()需要的內(nèi)存實際大小是9,又因為內(nèi)存的字節(jié)對齊是8,所以要跨兩個8字節(jié)對齊才足夠存儲一個Teacher實例。

1.1 原生指針 raw pointer

使用原生指針需要注意2點??:

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

// 定義一個未知類型的指針:本質(zhì)是分配32字節(jié)大小的空間,指定對齊方式是8字節(jié)對齊
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)

for i in 0...3 {
    p.storeBytes(of: i, as: Int.self) //存值
}

for i in 0...3 {
    //p是當前內(nèi)存的首地址,通過內(nèi)存平移來獲取值
    let value = p.load(fromByteOffset: i * 8, as: Int.self) //取值
    print("index: \(i), value: \(value)")
}

p.deallocate() //使用完需要手動釋放

預想值是打印的value是0~3,但實際情況卻是:

index: 0, value: 3
index: 1, value: 1152921504606846976
index: 2, value: 8319395793567416323
index: 3, value: 246290604621824

原因是沒有在指定的位置去設值,導致每次都對第一個內(nèi)存空間設值。
解決:存儲時,通過advanced(by:)指定的步長。

// 定義一個未知類型的指針:本質(zhì)是分配32字節(jié)大小的空間,指定對齊方式是8字節(jié)對齊
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)

for i in 0...3 {
    p.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i, as: Int.self) //移動對應的步長,存值
}

for i in 0...3 {
    //p是當前內(nèi)存的首地址,通過內(nèi)存平移來獲取值
    let value = p.load(fromByteOffset: i * 8, as: Int.self) //取值
    print("index: \(i), value: \(value)")
}

p.deallocate() //使用完需要手動釋放

/* print
index: 0, value: 0
index: 1, value: 1
index: 2, value: 2
index: 3, value: 3
*/
1.2 類型指針 typed pointer

1.2.1 通過 withUnsafePointer(to:) 方法獲取地址

class Teacher {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        var t = Teacher(name: "安老師")
        withUnsafePointer(to: &t) { print($0) }
        // 和上面是一樣的,只是方式不同
        withUnsafePointer(to: &t) { point in
            print(point)
        }
    }
}

輸出的是變量t的棧內(nèi)存地址(這與Teacher(name: "安老師")在堆內(nèi)存的地址不一樣)

如果我想訪問指針的具體內(nèi)容 pointee

// 得到的是Teacher實例
withUnsafePointer(to: &t) { print($0.pointee) } 
//  訪問Teacher實例的name屬性 - 安老師
withUnsafePointer(to: &t) { print($0.pointee.name) } 

如果我想改變屬性的值:

var t = Teacher(name: "安老師")
withUnsafePointer(to: &t) {
    return $0.pointee.name = "林老師"  // 把return省略也可以
}
print(t.name)

withUnsafePointer(to:)withUnsafeMutablePointer(to:)對于值類型不同:

var  age = 20
/* 報錯!不予以修改值
withUnsafePointer(to: &age) { point in
    point.pointee += 1  
}
*/
withUnsafeMutablePointer(to: &age) { point in
    point.pointee += 1
}

1.2.2 類型指針使用API:

泛型指針使用API

通過allocate創(chuàng)建UnsafeMutablePointer,需要注意以下幾點??:

  • initializedeinitialize需成對使用
  • deinitialize中的count與申請時的capacity需要一致
  • 使用完后必須deallocate
class Teacher {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        let tPoint = UnsafeMutablePointer<Teacher>.allocate(capacity: 3)
        defer {
            tPoint.deinitialize(count: 3)
            tPoint.deallocate()
        }
        tPoint.advanced(by: 0).initialize(to: Teacher(name: "黃老師"))
        tPoint.advanced(by: 1).initialize(to: Teacher(name: "劉老師"))
        tPoint.advanced(by: 2).initialize(to: Teacher(name: "潘老師"))
        // 三種方式訪問
        print(tPoint.pointee.name)                         // 黃老師   首地址指向就是第一個Teacher實例
        print(tPoint[1].name)                              // 劉老師
        print(tPoint.successor().pointee.name)             // 劉老師  同上一行是一樣效果的
        print(tPoint.successor().successor().pointee.name) // 潘老師
    }
}

有三種方式訪問:

  • 下標
  • 內(nèi)存平移
  • successor()
/** 三種方式是效果是一樣的 */
let tPoint = UnsafeMutablePointer<Teacher>.allocate(capacity: 3)
defer {
    tPoint.deinitialize(count: 3)
    tPoint.deallocate()
}
// 內(nèi)存平移
(tPoint + 1).initialize(to: Teacher(name: "劉老師"))
// successor()
tPoint.successor().initialize(to: Teacher(name: "劉老師"))
// advanced(by: 1)
tPoint.advanced(by: 1).initialize(to:  Teacher(name: "劉老師"))

為什么tPoint.advanced(by: 1)可以?為什么不用 tPoint.advanced(by: MemoryLayout<Teacher>.stride)?
注意:
關(guān)鍵在于這句代碼 --> let tPoint = UnsafeMutablePointer<Teacher>.allocate(capacity: 3),此時我們是知道tPoint的具體類型的,就是指向Teacher的指針。
在確定指針的類型后,通過步長的移動+1,就表示移動了那個類的實例大小空間+1。

2.內(nèi)存綁定

swift 提供了三種不同的 API 來綁定/重新綁定指針:

  • assumingMemoryBound(to:)
  • bindMemory(to: capacity:)
  • withMemoryRebound(to: capacity: body:)
2.1 assumingMemoryBound(to:) (只是讓編譯器繞過類型檢查,并沒有發(fā)?實際類型的轉(zhuǎn)換)

有些時候我們處理代碼的過程中,只有原始指針(沒有保留指針類型),但此刻對于處理代碼的我們來 說明確知道指針的類型,我們就可以使? assumingMemoryBound(to:) 來告訴編譯器預期的類型。

class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        var tuple = (10, 20)
        withUnsafePointer(to: &tuple) { tuplePtr in
            testPointer(tuplePtr)
        }
    }
}

func testPointer(_ p: UnsafePointer<Int>) {
    print(p[0])
    print(p[1])
}

此時在調(diào)用 testPointer會報編譯器錯誤 Cannot convert value of type 'UnsafePointer<(Int, Int)>' to expected argument type 'UnsafePointer<Int>'

而代碼修改一下使用 assumingMemoryBound(to:)

class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        var tuple = (10, 20)
        withUnsafePointer(to: &tuple) { tuplePtr in
            testPointer(UnsafeRawPointer(tuplePtr).assumingMemoryBound(to: Int.self))
        }
    }
}

func testPointer(_ p: UnsafePointer<Int>) {
    print(p[0])
    print(p[1])
}

元組時值類型,在本質(zhì)上這塊內(nèi)存空間是連續(xù)的,存放Int類型數(shù)據(jù)。先把元組轉(zhuǎn)化成指針指向元組的首地址tuple[0],通過assumingMemoryBound(to:)告訴編譯器,當前元組的內(nèi)存已經(jīng)綁定過Int了,這時就能騙過編譯器檢查

2.2 bindMemory(to: capacity:)

?于更改內(nèi)存綁定的類型,如果當前內(nèi)存還沒有類型綁定,則將?次綁定為該類型;否則重新綁定該類型,并且內(nèi)存中所有的值都會變成該類型。

案例:將一個class實例綁定成class的底層數(shù)據(jù)結(jié)構(gòu)

  • 定義一個Teacher類
class Teacher {
    var name: String
    init(name: String) {
        self.name = name
    }
}
  • 從swift源碼得出class的底層結(jié)構(gòu)是HeapObject,所以我們可以自定義一個HeapObject
struct HeapObject {
    var metadata: UnsafeRawPointer // 定義一個未知類型的指針
    var refCounts: UInt32 // 引用計數(shù)
}
  • 從swift源碼得出metadata其實也是一個結(jié)構(gòu)體,所以我自定義一個Metadata
struct Metadata { 
      var kind: Int 
      var superClass: Any.Type 
      var cacheData: (Int, Int) 
      var data: Int 
      var classFlags: Int32 
      var instanceAddressPoint: UInt32 
      var instanceSize: UInt32 
      var instanceAlignmentMask: UInt16 
      var reserved: UInt16 
      var classSize: UInt32 
      var classAddressPoint: UInt32 
      var typeDescriptor: UnsafeMutableRawPointer 
      var iVarDestroyer: UnsafeRawPointer
 }

將teacher綁定到結(jié)構(gòu)體內(nèi)存中:

  • 1.獲取實例變量teacher的內(nèi)存地址,聲明成非托管對象
  • 2.綁定到結(jié)構(gòu)體內(nèi)存,返回值是UnsafeMutablePointer<T>
  • 3.訪問成員變量
class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        let teacher = Teacher(name: "陳老師")
        // 通過Unmanaged指定內(nèi)存管理,類似于OC與CF的交互方式(所有權(quán)的轉(zhuǎn)換 __bridge)
        // passUnretained 不增加引用計數(shù),即不需要獲取所有權(quán)
        // passRetained 增加引用技術(shù),即需要獲取所有權(quán)
        // toOpaque 不透明的指針
        let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
        // bindMemory更改當前UnsafeMutableRawPointer的指針類型,綁定到具體類型值
        // - 如果沒有綁定,則綁定
        // - 如果已經(jīng)綁定,則重定向到 HeapObject類型上
        let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
        print(heapObject.pointee.metadata)
        print(heapObject.pointee.refCounts)
    }
}

打印輸出heapObject的成員

0x0000000109186768  // metadata輸出的就是地址
3

然后繼續(xù)對matadata進行綁定

class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        let teacher = Teacher(name: "陳老師")
        let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
        
        let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
        
        let metadataPtr = heapObject.pointee.metadata.bindMemory(to: Metadata.self, capacity: 1)
        print(metadataPtr.pointee)
    }
}

打印輸出metadata的成員值

Metadata(kind: 4536207176, superClass: _TtCs12_SwiftObject, cacheData: (4541967040, 140909287047168), data: 105553159911586, classFlags: 2, instanceAddressPoint: 0, instanceSize: 32, instanceAlignmentMask: 7, reserved: 0, classSize: 136, classAddressPoint: 16, typeDescriptor: 0x000000010e60e69c, iVarDestroyer: 0x0000000000000000)
2.3 withMemoryRebound(to: capacity: body:)(臨時更改內(nèi)存綁定類型)

當我們在給外部函數(shù)傳遞參數(shù)時,不免會有?些數(shù)據(jù)類型上的差距。如果我們進?類型轉(zhuǎn)換,必然要來回復制數(shù)據(jù);這個時候我們就可以使? withMemoryRebound(to: capacity: body:) 來臨時更 改內(nèi)存綁定類型。

class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        var num = 10
        withUnsafePointer(to: &num) { ptr: UnsafePointer<Int> in
            testPointer(ptr)
        }
   }     
}

func testPointer(_ p: UnsafePointer<Int8>) {
    print(p)
}

此時編譯器報錯因為類型不匹配。
使用withMemoryRebound(to: capacity: body:)修改后:

class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        var num = 10
        let ptr = withUnsafePointer(to: &num) {$0}
        ptr.withMemoryRebound(to: Int8.self, capacity: 1) { (ptr: UnsafePointer<Int8>)  in
            testPointer(ptr)
            // 超過了閉包作用域,則不是UnsafePointer<Int8>類型
        }
   }     
}

func testPointer(_ p: UnsafePointer<Int8>) {
    print(p)
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容