说说Swift中的指针

2,930 阅读6分钟

其实Swift中也有专门的指针类型,而且随处可见,比如在使用KVO时会看到的UnsafeMutableRawPointer就是对应C语言的void *

override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {               
}

Swift中的指针都是Unsafe的,分为两种:

一种是可以修改指针指向内容的MutablePointer

  1. UnsafeMutablePointer < Type > 泛型类指针 可以明确类型的指针 类似于C语言的 Type *
  2. UnsafeMutableRawPointer 非泛型类的 类似于C语言的 void *

如果只是想通过一个变量的指针来修改或者是查看所指向的内容可以这样做.声明一个函数,输入参数的变量的可变指针,就能通过这个指针来修改和访问指向的变量内存了.

var str = "Hello, playground"
func test1(_ ptr :UnsafeMutablePointer<String>){
    ptr.pointee += "  Swift"//泛型类的指针修改指针指向的类容
    
}

func test2(_ ptr :UnsafeMutableRawPointer){
    ptr.storeBytes(of: "Hello, Swift", as: String.self)//非泛型类的指针修改指针指向的内容
}
test1(&str)
print(str)//Hello, playground  Swift
test2(&str)
print(str)//Hello, Swift

另一种是不可以修改指针指向内容的指针类型

  1. UnsafePointer< Type > 泛型类 类似于C语言的 Const Type *
  2. UnsafeRawPointer 非泛型 类似于C语言的 Const Void *

使用方式:声明一个函数,输入参数为不可变指针,就能通过这个指针访问指向的变量内存了

func test3(_ ptr :UnsafePointer<String>){
    print(ptr.pointee)//泛型不可变指针访问此指针引用的实例。
}
var name  = "Anthony"
test3(&name)//Anthony
func test4(_ ptr :UnsafeRawPointer) {
    print(ptr.load(as: String.self))//非泛型不可变指针访问此指针引用的实例。
}
test4(&name)//Anthony

获取指向某个变量的指针

通过调用withUnsafeMutablePointer这个函数,能够获得对应变量的指针,使用方法如下

var score = 100
var ptr1 = withUnsafeMutablePointer(to: &score) { (point) -> UnsafeMutablePointer<Int> in
    return point
}
//可以简写成
var ptr2 = withUnsafeMutablePointer(to: &score){$0}

print("ptr1===",ptr1)//ptr1=== 0x000000010bda1530
print("ptr2===",ptr2)//ptr2=== 0x000000010bda1530

ptr1.pointee = 120//修改获取变量指针,引用的势力的值
var ptr3 = withUnsafeMutablePointer(to: &score) { UnsafeMutableRawPointer($0)//将泛型类指针转换成非泛型类指针
}
ptr3.storeBytes(of: 150, as: Int.self)

获取一个类对象在堆空间的地址值

获取一个类对象在堆空间的地址值分为三步:

  1. 获取指向对象内存地址的指针变量.
  2. 将指针指向的地址值取出.
  3. 通过指针指向的地址获得对象在内存中的地址.
var student  = Student.init(age: 20)
var ptr = withUnsafePointer(to: &student) { UnsafeRawPointer($0) }//首先获取到student这个变量的指针

let address = ptr.load(as: UInt.self)//将ptr指向的Student对象的内存地址值取出来

var objectPtr = UnsafeRawPointer(bitPattern: address)//这个就是student对象在堆空间的地址值

print(objectPtr)//Optional(0x0000600001866120)

创建一个指针

之前的内容都是获取一个已经存在的变量的指针,现在我们来学习如何创建一个指针.如果你只是需要储存Int类型,那可以直接使用malloc来分配一块内存地址,指向一个指针变量就行了

var ptr = malloc(24)//分配一块大小为24字节的内存,将地址值赋给ptr这个变量
// 存
ptr?.storeBytes(of: 2020, as: Int.self)//在开头的八个字节存2020
ptr?.storeBytes(of: 08, toByteOffset: 8, as: Int.self)//在8-16个字节存取08
ptr?.storeBytes(of: 01, toByteOffset: 16, as: Int.self)//在16-24个字节存取01
// 取
print((ptr?.load(as: Int.self))!) //2020
print((ptr?.load(fromByteOffset: 8, as: Int.self))!) // 8
print((ptr?.load(fromByteOffset: 16, as: Int.self))!) // 16
free(ptr)//注意这部分内存不是ARC管理,需要我们手动销毁

如果你希望生成一种指向各种数据类型的指针需要使用前面讲到的UnsafeMutablePointer

//创建指针
var genericPtr = UnsafeMutablePointer<String>.allocate(capacity: 3)//生成一个String类型的指针,容量大小为可以存储3个String类型的大小,比如在Swift中一个String占用16个字节,那么分配的大小就是48个字节,我们把这块内存分为三部分
//存内容
genericPtr.initialize(to: "Hello")//第一部分内存存储Hello
genericPtr.successor().initialize(to: "World")//使用successor来访问下一块分配的内存(也就是这块内存中的16-32字节的这部分)
genericPtr.successor().successor().initialize(to: "Swift")
// MARK: -取值方式和C语言的数组非常相似
print(genericPtr.pointee) // "Hello"
print((genericPtr + 1).pointee) // "World"
print((genericPtr + 2).pointee) // "Swift"
print(genericPtr[0]) // "Hello"
print(genericPtr[1]) // "World"
print(genericPtr[2]) // "Swift"
genericPtr.deinitialize(count: 3)
genericPtr.deallocate()//释放内存

创建指向自定义类的指针

//创建指针
var objcPtr = UnsafeMutablePointer<Teacher>.allocate(capacity: 3)//分配一个可以装下3个Teacher对象大小的内存,并且将内存地址赋值给objcPtr这个指针变量
//存
objcPtr.initialize(to: Teacher.init(gradeClass: "3年级 2班", name: "李老师"))
objcPtr.successor().initialize(to: Teacher.init(gradeClass: "2年级 1班", name: "王老师"))
(objcPtr + 2).initialize(to: Teacher.init(gradeClass: "1年级 5班", name: "张老师"))
//取
print(objcPtr.pointee.description)//3年级 2班 李老师
print(objcPtr.successor().pointee.description)//2年级 1班 王老师
print(objcPtr.successor().successor().pointee.description)//1年级 5班 张老师
//释放内存
objcPtr.deinitialize(count: 3)//将存放的3个Teacher对象
objcPtr.deallocate()//释放内存

指针转换

如果你声明了一个任性类型的指针,想把它装换成特定类型的指针可以使用unsafeBitCast来强制转换,下面举例说明.

class Father {
    var name = "王大狗"
    init() {       
    }    
}
class Son {
    var name = "王小狗"    
    init() {       
    }  
}

var ptr = UnsafeMutableRawPointer.allocate(byteCount: 64, alignment: 1)//分配一块大小为64字节的内存

ptr.assumingMemoryBound(to: Son.self).pointee = Son.init()//前面32个字节分配的son
(ptr + 32).assumingMemoryBound(to: Father.self).pointee = Father.init() //后面32个字节分配给father

print(unsafeBitCast(ptr, to: UnsafePointer<Son>.self).pointee.name)//这个指针本来是任性类型的类似于 void * 现在将其转化成UnsafePointer<Son>
//打印结果 王小狗
print(unsafeBitCast(ptr + 32 , to: UnsafePointer<Father>.self).pointee.name)//将任意类型的指针转化成 UnsafePointer<Father>
//打印结果 王大狗

// unsafeBitCast是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据 我们来举一个例子说明一下

//ptr指针之前指向的内存的前32个字节,存储的内容是一个Son的实例对象,现在我们强行将指针类型从指向Son类型的指针修改成指向Father类型的指针.
print(unsafeBitCast(ptr, to: UnsafePointer<Father>.self).pointee)//__lldb_expr_123.Son
//可以看到虽然指针已经修改成Father类型但是内存中存放的仍然是Son类型.
ptr.deallocate()

ptr指针之前指向的内存前32个字节是存储的是一个Son类型的实例对象,虽然我们将ptr指针转换成Father类型的指针,但是并不会改变内存数据.

相关的Swift代码放在这里了,大家可以对照文章查看.更加容易理解一些.

Swift指针先聊到这里吧,有什么错误或者是不足的地方,欢迎大家指出来.希望能多和大家讨论交流.