一、前言
我们在编写Objective-C和C代码时经常会用到指针,切换到Swift语言中来,虽然其可以无缝使用C语言指针,但是在语法上与OC和C还是有很大区别的。
Swift本身是内存安全的,只要确保所有变量在使用前都被正确地初始化,我们不必担心内存问题,因此,Apple官方不建议开发者直接操作内存,但是Swift还是为开发者提供了使用指针直接操作内存的方法,Swift中所有的指针类型都带有“Unsafe”前缀,就是为了提示开发者使用指针的行为是危险且不安全的,要慎重。
先来看看Swift中的指针类型:
C | Swift | 说明 |
---|---|---|
const Type * | UnsafePointer |
指针可变,指针指向的内存值不可变 |
Type * | UnsafeMutablePointer |
指针可变,指针指向的内存值可变 |
const void * | UnsafeRawPointer | 指针可变,指针指向的内存区域类型不确定,且内存值不可变 |
void * | UnsafeMutableRawPointer | 指针可变,指针指向的内存区域类型不确定,且内存值可变 |
StructType * | OpaquePointer | C中的一些自定义类型,Swift中无相应的类型,如:sqlite3_stmt结构体 |
ClassType const | UnsafePointer |
指向指针的指针,指针不可变,指针指向的类可变 |
ClassType __strong | UnsafeMutablePointer |
指向指针的指针,指针可变,指针指向的类可变 |
int8_t a[] | var x:[Int8] -> UnsafeBufferPointer | 数组指针,指向数组一个元素的地址 |
二、MemoryLayout
提到指针就不得不说一下内存模型。Swift的内存分配与C/C++/Objective-C/Java等类似:
- Stack: 存储值类型变量(如int、float、struct等),函数调用栈,存储引用类型的临时变量指针
- Heap: 存储引用类型的实例,比如类的对象
内存对齐:出于对寻址速度、原子操作以及简化CPU和Memory之间接口设计等方面的考虑,现代计算机系统对值类型的合法地址做了一些限制,要求某种数据类型的对象的地址必须是K的整数倍(K通常是2、4、8)。
因此对齐的原则是: 任何K字节的基本对象的地址必须是K的整数倍。
Tip: 在声明类或者结构体的成员变量时,可以将占用空间大的变量写在前面,将占用空间小的变量写在后面,以达到减少内存占用的目的。
MemoryLayout是Swift中用来计算数据类型占用内存空间大小的工具,它有三个比较常用的Int类型的属性:
- size/size(ofValue: T): T类型的实例占用连续内存字节的大小
- alignment/alignment(ofValue: T): 数据类型T的内存对齐原则,在64bit系统下,最大的内存对齐原则是8Byte
- stride/stride(ofValue: T): 由于内存对齐的原因,T类型的实例实际消耗的内存空间stride可能比其size大,浪费的内存空间即:stride - size
其简单用法如下:
1 | var count: Int = 0 |
我们看下Swift中基本数据类型的MemoryLayout三个属性的值:
1 | class Test: NSObject {} |
三、Swift指针
3.1、使用类型指针
1 | //MARK: - 使用类型指针 |
3.2、使用原生指针
1 | //MARK: - 使用原生指针 |
3.3、获取实例的bytes
在Objective-C中我们经常遇到函数接受bytes指针的参数,那么Swift中如何获取bytes指针呢?
1 | var str = "hello world." |
3.4、指针类型转换
我们操作C函数的时候,经常会遇到需要转换指针类型的情况,比如将指向结构体的指针转换为指向其它不同结构体的指针,这种操作在C语言中是很简单但也十分危险的。而由于Swift指针在创建时即明确了其类型,这就意味着一个UnsafePointer
1 |
|
四、实践——通过指针修改Struct类型实例的属性的值
先直接上代码:
1 | struct TestStruct { |
代码分析:
rawPtr
指针是一个void *
类型的指针,指向testStruct
实例所在内存的第一个字节,我们可以通过移动指针获取testStruct
的private
属性b
。
属性a
是Int8
类型,占用1个字节,但是由于内存对齐的原因,其所占的内存空间为8个字节,因此需要将rawPtr
向后移动8个字节才能获取到属性b
的起始地址:rawPtr.advanced(by: 8).assumingMemoryBound(to: Int.self)
。
assumingMemoryBound(to:)
Returns a typed pointer to the memory referenced by this pointer, assuming that the memory is already bound to the specified type.
因此,将rawPtr
向后移动8bytes后通过assumingMemoryBound(to: Int.self)
方法,可以得到指向属性b
的指针bPtr
。之后通过initialize(to: 10)
方法重新初始化属性b的内存区域,为其重新赋值。