Swift三部曲(一):指針的使用

背景

大部分情況下做Swift開發是不需要使用指針的,也不建議使用,但是有時候寫比較底層的東西就需要了。最近一段時間恰好我在寫的一些庫的需要用到指針,但是Swift關于指針的使用并沒有很詳細的文檔,導致寫起代碼來十分費勁,所以總結了一下。Runtime的文章很多,但是Swift的很少,所以我準備了Swift三部曲,介紹底層相關知識,有些之前看過了解過,但談不上很深入,所以會邊寫邊研究,分別是:

Swift三部曲(一):指針的使用
Swift三部曲(二):內存布局
Swift三部曲(三):方法派發

第一篇就是本文,第二篇和第三篇還沒寫,不過最近會陸續寫完。

MemoryLayout

// 單位均為字節
MemoryLayout<T>.size       // 類型T需要的內存大小
MemoryLayout<T>.stride     // 類型T實際分配的內存大小(由于內存對齊原則,會多出空白的空間)
MemoryLayout<T>.alignment  // 內存對齊的基數

指針分類

pointers.png
  • unsafe:不安全的,并不是真的不安全,大概是提示開發者少用。
  • Write Access:可寫入。
  • Collection:像一個容器,可添加數據。
  • Strideable:指針可使用 advanced 函數移動。
  • Typed:是否需要指定類型(范型)。

C 和 Swift關于指針的對照表:

C Swift 注解
const Type * UnsafePointer<Type> 指針可變,指針指向的內存值不可變
Type * UnsafeMutablePointer<Type> 指針和指針指向的內存值均可變
ClassType * const * UnsafePointer<UnsafePointer<Type>> 指針的指針:指針不可變,指針指向的類可變
ClassType ** UnsafeMutablePointer<UnsafeMutablePointer<Type>> 指針的指針:指針和指針指向的類均可變
ClassType ** AutoreleasingUnsafeMutablePointer<Type> 作為OC方法中的指針參數
const void * UnsafeRawPointer 指針指向的內存區,類型未定
void * UnsafeMutableRawPointer 指針指向的內存區,類型未定
StructType * OpaquePointer c語言中的一些自定義類型,Swift中并未有相對應的類型
int a[] UnsafeBufferPointer/UnsafeMutableBufferPointer 一種數組指針

typed pointer(類型指針)

Swift中的指針分為兩大類, typed pointer 指定數據類型指針, raw pointer 未指定數據類型的指針(原生指針)。

typed pointer
UnsafePointer
UnsafeMutablePointer
UnsafeBufferPointer
UnsafeMutableBufferPointer

UnsafeMutablePointer

被UnsafeMutablePointe引用的內存有三種狀態:

  1. Not Allocated:內存沒有被分配,這意味著這是一個 null 指針,或者是之前已經釋放過
  2. Allocated but not initialized:內存進行了分配,但是值還沒有被初始化
  3. Allocated and initialized:內存進行了分配,并且值已經被初始化

allocate

// 綁定類型并分配內存
// allocate是類方法
// capacity: Int表示向系統申請 capacity 個數的對應泛型類型的內存
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: MemoryLayout<Int>.stride)
// let pointer = UnsafeMutablePointer<CCInfo>.allocate(capacity: MemoryLayout.stride(ofValue: CCInfo()))

initialize

// 初始化, 對于Int, Float, Double這些基本數據類型,分配內存之后會有默認值0
pointer.initialize(to: 12)
// pointer.pointee 為 12

// 賦值
pointer.pointee = 10

deinitialize

// 與 initialize: 配對使用的 deinitialize: 用來銷毀指針指向的對象
// 回到初始化值之前,沒有釋放指針指向的內存,指針依舊指向之前的值
pointer.deinitialize(count: 1)

deallocate

// 與 allocate(capacity:) 對應的 deallocate() 用來釋放之前申請的內存
pointer.deallocate()

注意其實在這里對于 Int 這樣的在 C 中映射為 int 的 “平凡值” 來說,deinitialize 并不是必要的,因為這些值被分配在常量段上。但是對于像類的對象或者結構體實例來說,如果不保證初始化和摧毀配對的話,是會出現內存泄露的。所以沒有特殊考慮的話,不論內存中到底是什么,保證 initialize: 和 deinitialize 配對會是一個好習慣。

UnsafePointer

UnsafePointer<T> 是不可變的,C 中 const 修飾的指針對應 UnsafePointer (最常見的應該就是 C 字符串的 const char * 了)。

  • UnsafePointer中的pointee屬性只能get不能set。
  • UnsafePointer中沒有allocate方法。

初始化
可以由UnsafeMutablePointer、OpaquePointer或其他UnsafePointer創建一個UnsafePointer指針。其他與UnsafeMutablePointer類似。

//通過另一個變量指針初始化一個`UnsafePointer`常量指針
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: MemoryLayout<Int>.stride)
pointer.pointee = 20
let pointer1 = UnsafePointer<Int>.init(pointer)
print(pointer1.pointee)  // 20

將指針引用的內存作為不同的類型訪問

withMemoryRebound

將內存臨時重新綁定到其他類型。

var int8: Int8 = 123
let int8Pointer = UnsafeMutablePointer(&int8)
int8Pointer.withMemoryRebound(to: Int.self, capacity: 8) { ptr in
    print(ptr.pointee) // 123
}

bindMemory

該方法綁定內存為指定類型并返回一個UnsafeMutablePointer<指定類型>的指針,用到了指向內存的原始指針。

let intPointer = UnsafeRawPointer(int8Pointer).bindMemory(to: Int.self, capacity: 1)
print(intPointer.pointee) // 123

在使用 bindMemory方法將原生指針綁定內存類型,轉為類型指針的時候,一次只能綁定一個類型,例如:將一個原生指針綁定Int類型,不能再綁定Bool類型。

assumingMemoryBound

該方法意思是直接轉換這個原始指針為一個UnsafeMutablePointer<指定類型>的指針。

let strPtr = UnsafeMutablePointer<CFString>.allocate(capacity: 1)
let rawPtr = UnsafeRawPointer(strPtr)
let intPtr = rawPtr.assumingMemoryBound(to: Int.self)

UnsafeBufferPointer

UnsafeBufferPointer<T>表示一組連續數據指針。BufferPointer實現了Collection,因此可以直接使用Collection中的各種方法來遍歷操作數據,filter,map...,Buffer可以實現對一塊連續存在空間進行操作,類似C中的數組的指針。
但是同樣的,這個UnsafeBufferPointer是常量,它只能獲取到數據,不能通過這個指針去修改數據。與之對應的是UnsafeMutableBufferPointer指針。

var array = [1, 2, 3, 4]
// 遍歷
let ptr = UnsafeBufferPointer.init(start: &array, count: array.count)
ptr.forEach { element in
    print(element) // 1,2,3,4
}

//遍歷
array.withUnsafeBufferPointer {  ptr in
    ptr.forEach {
        print($0) // 1,2,3,4
    }
}

UnsafeBufferPointer 可以使用 baseAddress 屬性,這個屬性包含了緩沖區的基本地址。

let array: [Int8] = [65, 66, 67, 0]
puts(array)  // ABC
array.withUnsafeBufferPointer { (ptr: UnsafeBufferPointer<Int8>) in
    puts(ptr.baseAddress! + 1) //BC
}

let count = 2
let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
pointer.initialize(repeating: 0, count: count)

defer {
    pointer.deinitialize(count: count)
    pointer.deallocate()
}

pointer.pointee = 42 // pointer 指向的內存地址存放數值 42
pointer.advanced(by: 1).pointee = 6 // pointer 下一個內存地址存放數值 6,即 pointer 指向的起始地址加 Int 類型的步幅再移動 1 位,就其起始地址
   pointer.pointee
pointer.pointee
pointer.advanced(by: 1).pointee

let bufferPointer = UnsafeBufferPointer(start: pointer, count: count)
for (index, value) in bufferPointer.enumerated() {
    print("value \(index): \(value)") // value 0: 42, value 1: 6
}

UnsafeMutableBufferPointer

可變的序列指針,UnsafeMutableBufferPointer擁有對指向序列修改的能力:

let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count)
let bufferPointer = UnsafeMutableBufferPointer<Int>.init(start: pointer, count: 5)  // 拓展為5各元素的大小
bufferPointer[0] = 120
bufferPointer[1] = 130   //進行修改,其他未修改的內容將產生隨機值
bufferPointer.forEach { (a) in
    print(a) // 120, 130, 120054000649232, 73, 105553129173888
}
print(bufferPointer.count) // 5

狀況跟UnsafeBufferPointer有點類似,只是在初始化的時候,需要借助UnsafeMutablePointer。 并不能直接使用已經存在序列進行初始化。
值的注意的是:如果一個序列被初始化之后,沒有給每一個元素賦值的話,這些元素的值都將出現隨機值

raw pointer(原生指針)

raw pointer
UnsafeRawPointer
UnsafeMutableRawPointer
UnsafeRawBufferPointer
UnsafeMutableRawBufferPointer

UnsafeMutableRawPointer

UnsafeMutableRawPointer 用于訪問和操作非類型化數據的原始指針。

// 分配內存, byteCount: 表示總共需要的字節數, 表示 Int 類型的對齊方式
let pointer = UnsafeMutableRawPointer.allocate(byteCount: 4, alignment: MemoryLayout<Int>.alignment)

// 將給定值存儲在指定偏移量的原始內存中
pointer.storeBytes(of: 0x00060001, as: UInt32.self)

// 從pointer引用的內存  用UInt8實例加載(即第一個字節用UInt8實例加載)
let value = pointer.load(as: UInt8.self)
print(value) // 1

//    pointer.storeBytes(of: 42, as: Int.self)
//    let value = pointer.load(as: Int.self)
//    print(value) 42

let offsetPointer = pointer.advanced(by: 2)
// let offsetPoint = pointer + 2 // 偏移 2個字節, 如果偏移3個字節,下面的操作就會越界了
let offsetValue = offsetPointer.load(as: UInt16.self) // 將第三個和第四個字節作為UInt16實例加載

print(offsetValue) // 6
pointer.deallocate()

注:方法 storeBytes 和 load 分別是用來存儲和讀取字節數的。

UnsafeRawPointer

用于訪問非類型化數據的原始指針。UnsafeRawPointer只能由其他指針用init方法得到,與UnsafePointer類似,沒有allocate靜態方法。但是,與UnsafeMutableRawPointer類似的有兩種綁定方法bindMemory和assumingMemoryBound,綁定成UnsafePointer指針。

// 訪問不同類型的相同內存
var uint64: UInt64 = 257
let rawPointer = UnsafeRawPointer(UnsafeMutablePointer(&uint64))
let int64PointerT =  rawPointer.load(as: Int64.self)
let uint8Point = rawPointer.load(as: UInt8.self)

print(int64PointerT) // 257
print(uint8Point) // 1

// 257  = 1 0000 0001 而UInt8 表示存儲8個位的無符號整數,即一個字節大小, 2^8 = 256, [0, 255], 超出8個位范圍的無法加載,所以打印為1

UnsafeRawBufferPointer 與 UnsafeMutableRawBufferPointer

引用Swift內存賦值探索二: 指針在Swift中的使用的描述:

UnsafeRawBufferPointer和UnsafeMutableRawBufferPointer 指代的是一系列的沒有被綁定類型的內存區域。我們可以理解成他們實際上就是一些數組,再綁定內存之前,其中包含的元素則是每一個字節。
在底層,基本數據單元的復制是有效的,另外沒有被 retain 和 stong 的也是能夠安全的復制的,同樣的,對于來自C API的對象也能夠安全的復制。對于原聲的Swift類型,有的包含了引用的對象的復制則有可能失敗,但是我們可以使用指針對他們的值進行復制,這樣的結果是有效的。如果我們強行對一下發類型進行復制,不一定有效,除非使用像C語言中的APImemmove().來操作

UnsafeRawBufferPointer和UnsafeMutableRawBufferPointer是內存視圖,盡管我們知道它指向的內存區域,但是它并不擁有這塊內存的引用。復制UnsafeRawBufferPointer 類型的變量不會復制它的內存;但是初始化一個集合到另一個新的集合過程會復制集合中的引用內存。

總結:

  1. 內存中的每個字節都被視為一個獨立于內存綁定類型的 UInt8 值, 與該內存中保存的值的類型無關。
  2. UnsafeRawBufferPointer / UnsafeMutableRawBufferPointer 實例是內存區域中原始字節的視圖。
  3. 通過原始緩沖區從內存中讀取是一種無類型操作, UnsafeMutableRawBufferPointer 實例可以寫入內存, UnsafeRawBufferPointer 實例不可以。
  4. 如果要類型化,必須將內存綁定到一個類型上。
let pointer = UnsafeMutableRawBufferPointer.allocate(byteCount: 3, alignment: MemoryLayout<Int>.alignment)
pointer.copyBytes(from: [1, 2, 3])
pointer.forEach {
    print($0) // 1, 2, 3
}

Memory Access

要通過類型化操作訪問底層內存,必須將內存綁定到一個簡單的類型。

typed pointer
withUnsafePointer
withUnsafeMutablePointers
withUnsafeBytes
withUnsafeMutableBytes

withUnsafePointer/withUnsafeMutablePointer

Swift 中不能像 C 里那樣使用 & 符號直接獲取地址來進行操作。如果我們想對某個變量進行指針操作,我們可以借助 withUnsafePointer 或 withUnsafeMutablePointer 這兩個輔助方法。withUnsafePointer 或 withUnsafeMutablePointer 的差別是前者轉化后的指針不可變,后者轉化后的指針可變。

基本數據類型

var a = 0

withUnsafePointer(to: &a) { ptr in
    print(ptr) // 0x00007ffeeccb3b40
}

a = withUnsafePointer(to: &a) { ptr in
    return ptr.pointee + 2
    // 此時, 會新開辟空間, 令a指向新地址, 值為2,
}

// 修改指針指向的內存值
var b = 42
withUnsafeMutablePointer(to: &b) { ptr in
    ptr.pointee += 100   // 未開辟新的內存空間, 直接修改a所指向的內存值
}
print(b)   // 142

var arr = [1, 2, 3]
withUnsafeMutablePointer(to: &arr) { ptr in
    ptr.pointee[0] = 10
}
print(arr)   // [10, 2, 3]

arr.withUnsafeBufferPointer { ptr in
    ptr.forEach{
        print("\($0)")  // 10 2 3
    }
}

// 修改內存值
arr.withUnsafeMutableBufferPointer { ptr in
    ptr[0] = 100

    ptr.forEach {
        print("\($0)") // 100 2 3
    }
}


獲取 struct 類型實例的指針


struct User {
    var name: Int = 5

    init(name: Int = 5) {
        self.name = name
    }
}

var user = User()

let pointer = withUnsafeMutablePointer(to: &user, {$0})
print(user) // user
pointer.pointee = User(name: 10)
print("\(pointer.pointee)") // User(name: 10)
print(user) // User(name: 10)

獲取 class 類型實例的指針
獲取 class 類型實例的指針和上面不同,不是使用withUnsafePointer 或 withUnsafeMutablePointer,而是使用下面講到的Unmanaged,之所以放在這里,是想因為這里講到獲取對象指針,所以附帶講一下。

func headPointerOfClass() -> UnsafeMutablePointer<Int8> {
    let opaquePointer = Unmanaged.passUnretained(self as AnyObject).toOpaque()
    let mutableTypedPointer = opaquePointer.bindMemory(to: Int8.self, capacity: MemoryLayout<Self>.stride)
    return UnsafeMutablePointer<Int8>(mutableTypedPointer)
}

withUnsafeBytes/withUnsafeMutableBytes

可以使用withUnsafeBytes/withUnsafeMutableBytes獲取實例的字節數。

// 打印字符串
let string = "hello"
let data = string.data(using: .ascii)
data?.withUnsafeBytes{ (ptr: (UnsafePointer<Int8>)) in
    print(ptr.pointee) // 104 = 'h'
    print(ptr.advanced(by: 1).pointee)  // 101 = 'e'
}


// 打印結構體
struct SampleStruct {
    let number: UInt32
    let flag: Bool
}

MemoryLayout<SampleStruct>.size       // returns 5
MemoryLayout<SampleStruct>.alignment  // returns 4
MemoryLayout<SampleStruct>.stride     // returns 8

var sampleStruct = SampleStruct(number: 25, flag: true)

withUnsafeBytes(of: &sampleStruct) { bytes in
    for byte in bytes {
        print(byte) // 25 0 0 0 1
    }
}

let bytes = withUnsafeBytes(of: &sampleStruct) { bytes in
    return bytes // 這里會有奇怪的bug!
}

print("Horse is out of the barn!", bytes) // undefined !!! 

注:

  1. 不要從 withUnsafeBytes 中返回指針。
  2. 絕對不要讓指針逃出 withUnsafeBytes(of:) 的作用域范圍。這樣的代碼會成為定時炸彈,你永遠不知道它什么時候可以用,而什么時候會崩潰。

Unmanaged<T>(非托管對象)

如果直接使用指針,那么就需要我們手動管理內存,這個并不好辦,所以蘋果引入了Unmanaged來管理引用計數,Unmanaged 能夠將由 C API 傳遞過來的指針進行托管,我們可以通過Unmanaged標定它是否接受引用計數的分配,以便實現類似自動釋放的效果;同時,如果不是使用引用計數,也可以使用Unmanaged 提供的release函數來手動釋放,這比在指針中進行這些操作要簡單很多。關于Unmanaged swifter.tips這篇TOLL-FREE BRIDGING 和 UNMANAGED文章好像很多地方都講錯了。

一個 Unmanaged<T> 實例封裝有一個 CoreFoundation 類型 T,它在相應范圍內持有對該 T 對象的引用。

將一個對象聲明為非托管方法有兩種:

  • passRetained:增加它的引用計數。
  • passUnretained:不增加它的引用計數。

從一個 Unmanaged 實例中獲取一個 Swift 值的方法有兩種:

  • takeRetainedValue():返回該實例中 Swift 管理的引用,并在調用的同時減少一次引用次數。
  • takeUnretainedValue():返回該實例中 Swift 管理的引用而 不減少 引用次數。

這看起來還是不知道何時使用passRetained和passUnretained,何時使用takeRetainedValue和takeUnretainedValue,蘋果提出了Ownership Policy

  • 如果一個函數名中包含Create或Copy,則調用者獲得這個對象的同時也獲得對象所有權,返回值Unmanaged需要調用takeRetainedValue()方法獲得對象。調用者不再使用對象時候,Swift代碼中不需要調用CFRelease函數放棄對象所有權,這是因為Swift僅支持ARC內存管理,這一點和OC略有不同。
  • 如果一個函數名中包含Get,則調用者獲得這個對象的同時不會獲得對象所有權,返回值Unmanaged需要調用takeUnretainedValue()方法獲得對象。
let bestFriendID = ABRecordID(...)

// Create Rule - retained
let addressBook: ABAddressBook = ABAddressBookCreateWithOptions(nil, nil).takeRetainedValue()

if let
    // Get Rule - unretained
    bestFriendRecord: ABRecord = ABAddressBookGetPersonWithRecordID(addressBook, bestFriendID)?.takeUnretainedValue(),
    // Create Rule (Copy) - retained
    name = ABRecordCopyCompositeName(bestFriendRecord)?.takeRetainedValue() as? String
{
    println("\(name): BFF!")
    // Rhonda Shorsheimer: BFF!
}

這些函數可以通過函數名知道該怎么使用Unmanaged,但很多時候在使用的不是這種命名的C函數,

Alamofire的NetworkReachabilityManager.swift中就有一段調用C方法使用了Unmanaged。

@discardableResult
    open func startListening() -> Bool {
        var context = SCNetworkReachabilityContext(version: 0, info: nil, retain: nil, release: nil, copyDescription: nil)
        context.info = Unmanaged.passUnretained(self).toOpaque()

        let callbackEnabled = SCNetworkReachabilitySetCallback(
            reachability,
            { (_, flags, info) in
                let reachability = Unmanaged<NetworkReachabilityManager>.fromOpaque(info!).takeUnretainedValue()
                reachability.notifyListener(flags)
            },
            &context
        )

        let queueEnabled = SCNetworkReachabilitySetDispatchQueue(reachability, listenerQueue)

        listenerQueue.async {
            self.previousFlags = SCNetworkReachabilityFlags()
            self.notifyListener(self.flags ?? SCNetworkReachabilityFlags())
        }

        return callbackEnabled && queueEnabled
    }

因為self對象的使用是在當前作用域內,也就是startListening方法內部,我們能保證使用的時候對象一直存活,所以使用的passUnretained和takeUnretainedValue。

class Person {
    func eat() {
        print(#file, #line, "eat now")
    }
}

func callbackFunc(userPtr: UnsafeMutableRawPointer?) {
    if userPtr == nil { return }
    let user = Unmanaged<Person>.fromOpaque(userPtr!).takeRetainedValue()
    user.eat()
}
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
        
        var user = Person()
        let userPtr = Unmanaged<Person>.passRetained(user).toOpaque()
        callback(callbackFunc, userPtr)
    }
}

我們看到由于使用了閉包,user對象的使用超出了當前作用域也就是viewDidLoad方法,所以需要使用takeRetainedValue和passRetained。

let userPtr = Unmanaged<Person>.passRetained(user).toOpaque()

使用passRetained()會創建一個被retained的指向這個對象的指針,這樣可以保證在C中被調用的時候這個對象還在那,不會被銷毀,這個方法會產生一個Unmanaged的實例變量,然后通過toOpaque() 方法轉換為 UnsafeMutableRawPointer。

 let user = Unmanaged<Person>.fromOpaque(userPtr!).takeRetainedValue()

利用Unmanaged相反的方法,取出user對象,這種方法更加安全,可以保證對象在傳遞過程中一直存在,并且直接獲得對象。

非托管對象使用周期超過了編譯器認為的生命周期,比如超出作用域,必須手動 retain 這個對象,也就是使用 passRetained 方法。一旦你手動 retain 了一個對象,就不要忘記 release 掉它,方法就是調用非托管對象的 release 方法,或者用 takeRetainedValue 取出封裝的對象,并將其管理權交回 ARC。但注意,一定不要對一個用 passUnretained 構造的非托管對象調用 release 或者 takeRetainedValue,這會導致原來的對象被 release 掉,從而引發異常。

測試

unmanaged還是有點難的,我在其他地方看到這段代碼,大家可以在Playground試一試,如果有知道所有答案的,可以留言討論一下。

class SomeClass {
    let text: Int

    init(text: Int) {
        self.text = text
    }

    deinit {
        print("Deinit \(text)")
    }
}

do {
    let unmanaged = Unmanaged.passRetained(SomeClass(text: 0))
    unmanaged.release()
}

do {
    let _ = Unmanaged.passUnretained(SomeClass(text: 1))
}

do {
    let unmanaged = Unmanaged.passRetained(SomeClass(text: 2))

    let _ = unmanaged.retain()
    unmanaged.release()

    Unmanaged<SomeClass>.fromOpaque(unmanaged.toOpaque()).release()

    unmanaged.release()
}

do {
    let opaque = Unmanaged.passRetained(SomeClass(text: 3)).toOpaque()
    Unmanaged<SomeClass>.fromOpaque(opaque).release()
}

do {
    let unmanaged = Unmanaged.passRetained(SomeClass(text: 4))
    let _ = unmanaged.takeUnretainedValue()
    unmanaged.release()
}

do {
    let unmanaged = Unmanaged.passRetained(SomeClass(text: 5))
    let _ = unmanaged.takeRetainedValue()
}

函數指針

在C中有回調函數,當swift要調用C中這類函數時,可以使用函數指針。Swift中可以用@convention 修飾一個閉包

類型 注解
@convention(swift) 表明這個是一個swift的閉包
@convention(block) 表明這個是一個兼容oc的block的閉包,可以傳入OC的方法
@convention(c) 表明這個是兼容c的函數指針的閉包,可以傳入C的方法

第二個類型是這三個當中可能最常用的,當你在Swift使用Aspects中會用到,我用Swift寫的Aspect使用的時候也可以這樣:

let wrappedBlock: @convention(block) (AspectInfo, Int, String) -> Void = { aspectInfo, id, name in

}
let block: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self)
test.hook(selector: #selector(Test.test(id:name:)), strategy: .before, block: )

unsafeBitCast

我們可以看看源碼Builtin.swift

/// - Parameters:
///   - x: The instance to cast to `type`.
///   - type: The type to cast `x` to. `type` and the type of `x` must have the
///     same size of memory representation and compatible memory layout.
/// - Returns: A new instance of type `U`, cast from `x`.
@inlinable // unsafe-performance
@_transparent
public func unsafeBitCast<T, U>(_ x: T, to type: U.Type) -> U {
  _precondition(MemoryLayout<T>.size == MemoryLayout<U>.size,
    "Can't unsafeBitCast between types of different sizes")
  return Builtin.reinterpretCast(x)
}

unsafeBitCast 是非常危險的操作,它會將一個指針指向的內存強制按位轉換為目標的類型,并且只進行了簡單的 size 判斷。因為這種轉換是在 Swift 的類型管理之外進行的,因此編譯器無法確保得到的類型是否確實正確,你必須明確地知道你在做什么。


let block: AnyObject = unsafeBitCast(wrappedBlock, to: AnyObject.self)

這段代碼就是將oc的block的閉包轉成Anyobject類型。

參考文章:
Unsafe Swift: Using Pointers And Interacting With C
UnsafeRawPointer Migration
Swift內存賦值探索二: 指針在Swift中的使用
Unmanaged
在Swift 3.0調用C語言API
Swift 中的指針使用
深度探究HandyJSON(一) Swift 指針的使用

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

推薦閱讀更多精彩內容