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

栏目: C · 发布时间: 6年前

内容简介:大部分情况下做Swift开发是不需要使用指针的,也不建议使用,但是有时候写比较底层的东西就需要了。最近一段时间恰好我在写的一些库的需要用到指针,但是Swift关于指针的使用并没有很详细的文档,导致写起代码来十分费劲,所以总结了一下。Runtime的文章很多,但是Swift的很少,所以我准备了Swift三部曲,介绍底层相关知识,有些之前看过了解过,但谈不上很深入,所以会边写边研究,分别是:Swift三部曲(一):指针的使用Swift三部曲(二):内存布局

大部分情况下做Swift开发是不需要使用指针的,也不建议使用,但是有时候写比较底层的东西就需要了。最近一段时间恰好我在写的一些库的需要用到指针,但是Swift关于指针的使用并没有很详细的文档,导致写起代码来十分费劲,所以总结了一下。Runtime的文章很多,但是Swift的很少,所以我准备了Swift三部曲,介绍底层相关知识,有些之前看过了解过,但谈不上很深入,所以会边写边研究,分别是:

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

Swift三部曲(二):内存布局

Swift三部曲(三):方法派发

第一篇就是本文,第二篇和第三篇还没写,不过最近会陆续写完。

MemoryLayout

// 单位均为字节
MemoryLayout<T>.size       // 类型T需要的内存大小
MemoryLayout<T>.stride     // 类型T实际分配的内存大小(由于内存对齐原则,会多出空白的空间)
MemoryLayout<T>.alignment  // 内存对齐的基数
复制代码

指针分类

Swift三部曲(一):指针的使用
  • 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 是不可变的,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表示一组连续数据指针。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 实例封装有一个 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类型。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Is Parallel Programming Hard, And, If So, What Can You Do About

Is Parallel Programming Hard, And, If So, What Can You Do About

Paul E. McKenney

The purpose of this book is to help you understand how to program shared-memory parallel machines without risking your sanity.1 By describing the algorithms and designs that have worked well in the pa......一起来看看 《Is Parallel Programming Hard, And, If So, What Can You Do About 》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具