「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」
指针的安全性
- 创建一个对象的时候,需要在
堆分配内存空间。但是对象的生命周期是有限的,即内存空间的生命周期是有限的,这也就意味着如果我们使用指针指向这块内存空间,如果当前内存空间的生命周期结束了(引用计数为0),那么当前的指针将会称为野指针; - 创建的内存空间是有边界的,如果我们创建了一个大小为
N的数组,当我们通过指针访问到N+1的位置时,此时我们访问了一个未知的内存空间,将会产生越界; - 指针类型与内存的值类型有可能不一致;
指针是不安全的
指针类型
Swift中的指针分为两类:
typed pointer指定数据类型指针;raw pointer未指定数据类型的指针(又称为原生指针);
我们在开发过程中使用到的指针类型基本上如下表:
| Swift | Object-C | 备注 | |
|---|---|---|---|
| unsafePointer | const T * | 指针及其指向的内容都不可变 | 指定类型数据指针;T用来指定数据类型 |
| unsafeMutablePointer | T * | 指针及其指向的内容均可变 | |
| unsafeRawPointer | const void * | 指针指向的内存区域未定 | 内容不可变 |
| unsafeMutableRawPointer | void * | 指针指向的内存区域未定 | 内容可变 |
| unsafeBufferPointer | 连续的不可变内存空间 | 指定类型 | |
| unsafeMutableBufferPointer | 连续的可变内存空间 | 指定类型 | |
| unsafeRawBufferPointer | 原生的连续的不可变内存空间 | 未指定类型 | |
| unsafeMutableRawBufferPointer | 原生的连续的可变内存空间 |
原生指针的使用
我们使用Raw Pointer来存储4个整形的数据,此时我们需要使用到UnsafeMutableRawPointer,代码如下:
我们注意到在上述代码中有这样一段代码:
p.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i, as: Int.self)
as: Int.self是告诉系统,我们存储的数据的类型
如果缺少了advanced那么结果就会出错,这是因为我们在内存中存储数据的时候,需要根据数据类型偏移对应的步长大小;所以在存储数据的时候,需要以p为基地址,每次存储数据都需要偏移当前数据类型的步长 * 索引;
那么MemoryLayout是用来做什么的呢?
MemoryLayout
我们来通过下边一个例子来讲解一下MemoryLayout的用法:
size:当前类型在内存中的实际大小:8 + 16 + 1 = 25stride:当前类型在内存中的步长,需要考虑内存对齐:8 + 16 + 8(1补齐为8) = 32- 比如当我们需要在内存中连续存储两个
Teacher类型的结构体时,从第一个Teacher到第二个Teacher中间指针需要移动的大小即为步长;
- 比如当我们需要在内存中连续存储两个
alignment:内存对齐大小;
泛型指针的使用
泛型指针通常也称为类型指针,相较于原生指针来说,泛型指针已经制定了指针的类型;
**0x0000000100008080**就是我们得到的age变量的内存指针;
对于age,如果我们想要修改它应该如何做呢?
pointee可以得到指针执行的数据的数据类型;
需要注意的是,在withUnsafePointer的尾随闭包中是无法修改ptr.pointee的,如下所示:
在
withUnsafePointer中返回的pointee是不可变的;
如果我们想要修改ptr.pointee,需要使用withUnsafeMutablePointer:
withUnsafePointer返回不可变的指针,指针的内容也不可变;withUnsafeMutablePointer返回可变的指针,指针的内容可变;
指针的操作
了解了指针相关的知识,我们就可以通过指针对内存进行操作,我们来看一下下边一段代码:
在上述代码中,我们创建了一块内存空间,该内存空间允许存放6个Person类型的结构体;因为我们已经知道了指针的类型,所以我们通过UnsafeMutablePointer来创建内存空间;
ptr相当于我们创建的内存空间的首地址,通过ptr我们可以对该内存区域进行操作;deinitialize和deallocate成对出现,用来回收内存;deinitialize可以理解为将当前内存空间全部置为0;