Swift中的指針

指針

Swift中指針分為兩類:

  • typed pointer:指定數據類型指針,UnsafePointer<T>,T表示泛型
  • raw pointer:未指定數據類型指針(原生指針),UnsafeRawPointer

Swift 中的指針和 OC 中指針的對應關系如下:

  • Raw Pointer的使用

注意: 指針的內存管理需要手動管理,使用完后需要手動釋放

例:使用Raw Pointer在內存中連續存儲4個整型數據

// 以8字節對齊,分配32字節大小的內存空間
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)

// 存儲值到內存
for i in 0..<4 {
    // 通過advanced(by:)指定存儲時的步長
    p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
}

// 從內存中讀取值
for i in 0..<4 {
    // 通過內存平移來讀取內存中的值
    let value = p.load(fromByteOffset:i * 8, as: Int.self)
    print("index\(i), value:\(value)")
}
// 指針的內存管理需要手動管理
p.deallocate()
  • Type Pointer的使用
  • withunsafePointer(to:)

首先了解Swift$0$1的含義:
Swift自動為閉包提供參數名縮寫功能,可以直接通過$0$1 來表示閉包中的第一個、第二個參數,并且對應的參數類型會根據函數類型來進行判斷。

在前面,我們獲取一個變量本身的地址都是通過 withUnsafePointer(to:) 的方式獲取。
其函數定義如下:

@inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result

可看到:

  • 第一個參數是通過 inout 修飾的泛型,前面說過 inout 用來修飾的參數表示傳遞的是地址
  • 第二個參數是一個閉包表達式,通過 rethrows 重新拋給 Result(即閉包表達式產生的結果),所以可以通過簡寫閉包的參數返回值,簡寫如下:
var age : Int = 10

// 第一種:單一表達式:相當于將ptr賦值給p,p的類型UnsafePointer<Int>
let p = withUnsafePointer(to: &age) { ptr in
    return ptr
}

// 第二種:此時p為print($0)的返回結果,類型為print方法的返回類型Void
let p = withUnsafePointer(to: &age) { print($0) }

// 第三種:$0賦值給p,p的類型UnsafePointer<Int>
let p = withUnsafePointer(to: &age) { $0 }

從上可知:withUnsafePointer 這個函數result 類型取決于其參數閉包表達式的返回值,第一種和第三種得到的 p 都是UnsafePointer<Int>類型,可以通過 p.pointee 獲取 age 的值。

還可以使用 withUnsafePointer 這個函數修改 age 的值:

age = withUnsafePointer(to: &age, { ptr in
    return ptr.pointee + 12
})
  • withUnsafeMutablePointer(to:)
    如果想在閉包表達式中直接修改age的值則需要用到 withUnsafeMutablePointer
withUnsafeMutablePointer(to: &age, { ptr in
    ptr.pointee = 122
})

執行 上面代碼,age 的值就直接被修改為 122 了。

  • UnsafeMutablePointer
// 分配容量為1的內存空間(Int8字節)
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)

// 初始化
ptr.initialize(to: 22)

// 手動管理內存:對應initialize
ptr.deinitialize(count: 1)

// 手動管理內存:對應allocate
ptr.deallocate()

這時 ptr.pointee 讀出來則為22。

指針實例演示

實例1:訪問結構體指針

struct YYTeacher {
    var age = 12
    var name = "YY"
}

var t = YYTeacher()

// 分配裝2個struct的內存空間
let ptr = UnsafeMutablePointer<YYTeacher>.allocate(capacity: 2)

// 初始化第一個struct的內存空間
ptr.initialize(to: YYTeacher())

// 第一種方式:初始化第二個struct的內存空間
ptr.successor().initialize(to: YYTeacher(age: 22, name: "EE"))

// 第二種方式:初始化第二個struct的內存空間
//(ptr + 1).initialize(to: YYTeacher(age: 22, name: "EE"))

// 第三種方式:初始化第二個struct的內存空間
//ptr.advanced(by: 1).initialize(to:YYTeacher(age: 22, name: "EE"))

// 兩種方式獲取第一個struct的值
print(ptr[0])
print(ptr.pointee)

// 三種方式獲取第二個struct的值
print(ptr[1])
print((ptr + 1).pointee)
print(ptr.successor().pointee)
print(ptr.advanced(by: 1).pointee)

// 手動管理內存:對應initialize
ptr.deinitialize(count: 2)

// 手動管理內存:對應allocate
ptr.deallocate()

通過查看源碼可知:successor() 本質就是 advanced(by:1)

對比上面 Raw Pointer 例子中的p.advanced(by: i * 8) ,因為上面的 p未知類型,要存儲Int類型的值就必須每次指定移動 8 的倍數,這里的8是一個Int類型所占的字節數;而在 UnsafeMutablePointer 中初始化第二個struct內存空間時使用 ptr.advanced(by:1) ,這里已知 ptr結構體指針,只需要告知往前移動幾步就可以了,這里的 1 相當于往前移動 1個struct步長 (這里的 struct 步長為24)就好,兩個 struct 相差的字節大小為1 * 24
實例2:將實例對象綁定到結構體內存中

struct HeapObject {
    var kind: Int
    var strongRef: UInt32
    var unownedRef: UInt32
}

class YYTeacher {
    var age = 20
}

var t = YYTeacher()

首先:獲取實例對象的值
其次:將 raw pointer 綁定到具體類型指針

// 獲取實例對象的值,返回值類型為 UnsafeMutableRawPointer
let ptr = Unmanaged.passUnretained(t as AnyObject).toOpaque()

// 將當前Raw Pointer-->ptr重新綁定到HeapObject結構體,返回值類型為 UnsafeMutablePointer<HeapObject>
let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
// 訪問內存中的值
print(heapObject.pointee)
  • Unmanaged 取代手動管理內存,用來托管指針,可以指定內存管理,類似于 OCCF 交互時的 CoreFoundation( __brige ,有所有權的轉換),在這里將實例對象 t 聲明成了非托管 對象給到 ptr
  • passRetained增加引用計數,需要獲取所有權,如OC中的create、copy關鍵字在聲明過程中就需要所有權
  • passUnretained不增加引用計數,不需要獲取其所有權,不再需要手動去釋放如 CF 中的 CFRelese,在這里只需要獲得其指針,不需要獲取其所有權
  • bindMemory
    如果綁定,則綁定
    如果綁定,則重定向到指定類型內存
  • 如果這里將 var kind: Int 改成 var kind: UnsafeRawPointer ,在print(heapObject.pointee.kind) 時打印的則是地址

擴展:如何將 heapObject 的屬性 kind 綁定到 Swift 類結構的結構體內存中?(此時的 kindUnsafeRawPointer 類型)

  • 首先定義個swift類結構的結構體:
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 instanceAlignMask:UInt16
    var reserved:UInt16
    var classSize:UInt32
    var classAddressOffset:UInt32
    var description: UnsafeRawPointer
}
  • kind 綁定到 swift_class 這個結構體的內存中
// 綁定內存
let metaptr = heapObject.pointee.kind.bindMemory(to: swift_class.self, capacity: 1)
// 訪問內存中的值
print(metaptr.pointee)

運行結果如下圖:

其本質是因為 metaptrswift_class 的內存結構是一樣的。

實例3:元祖指針類型轉換
如何將元祖 tul 的指針傳給 testPointer 函數?

var tul = (10,20)

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

在上面例子中,testPointer 函數的參數是一個UnsafePointer<(Int)> 類型的,而 tul 的指針類型為 UnsafePointer<(Int, Int)> ,這時就需要將 tul 的指針類型重新綁定內存。如下:

withUnsafePointer(to: &tul) {(tulPtr : UnsafePointer<(Int, Int)>) in
    // 這里不能使用bindMemory,因為tulPtr已經綁定到具體類型了
    // assumingMemoryBound:假定內存綁定,告訴編譯器tulPtr已經綁定過Int類型了,不需要再檢查memory綁定
    testPointer(UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
}

那么:bindMemory(to:capacity:)assumingMemoryBound(to:)以及withMemoryRebound(to:capacity:_:)用法有什么區別呢?

  • bindMemory(to:capacity:):如果以前未綁定,調用后則首次綁定到指定類型的內存;如果以前已綁定,調用后則重新綁定到指定類型(這里編譯器會檢查之前綁定的類型和現在指定的類型是否布局兼容)。

  • assumingMemoryBound(to:)假定內存綁定,重新綁定到指定類型的內存,告訴編譯器不用檢查類型(繞過編譯器檢查類型)

  • withMemoryRebound(to:capacity:_:)臨時更改內存綁定類型。適用于已初始化的類型指針;使用此方法時,重新綁定的類型要與之前的指針類型具有相同 sizestride

var age = 12

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

let ptr = withUnsafePointer(to: &age){$0}
ptr.withMemoryRebound(to: UInt64.self, capacity: 1) {
   testPointer($0)
}

在這里 ptr 本身是 Int 類型的指針,使用 withMemoryRebound臨時將其指針類型綁定到 UInt64 類型的內存,使用結束后,其類型重新綁定成 Int 類型的指針。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Apple 期望在 Swift 中指針能夠盡量減少登場幾率,因此在 Swift 中指針被映射為了一個泛型類型,并且...
    在ios寫bug的杰克閱讀 875評論 0 4
  • 本文概要 指針的種類及區別 不同指針間的相互轉換及常用方法 各種類型的指針獲取及應用 more than that...
    01_Jack閱讀 1,574評論 0 6
  • Swift 中的指針使用 本文轉載自OneV's Den的blogApple 期望在 Swift 中指針能夠盡量減...
    hoggenWang閱讀 657評論 0 0
  • 雖然在Swift中是不推薦直接訪問指針的,但是Swift還是保留了訪問指針的方法,當然,完全拋棄C一套的東西還是相...
    TomatosX閱讀 2,760評論 0 1
  • 第三節課:指針 指針 swift中的指針分為兩類 typed pointer 指定數據類型指針,即 UnsafeP...
    不說ryo閱讀 362評論 0 0