在OC
中我们对指针的分类和结构都进行了比较详细的研究,那Swift
中的指针又是怎样的,又是怎样使用的?本文将对它的类型,创建以及使用进行一一介绍
Swift指针简介
Swift
中的指针分为两类,指定数据类型指针(typed pointer
) 和 未指定数据类型指针(raw pointer
),raw pointer
也称为原指针raw pointer
指针在swift
中是用UnsafeRawPointer
来表示typed pointer
在swift
中是用UnsafePointer<T>
来表示,它是泛型,T
是要指定的类型。- 可以发现
swift
指针都带有Unsafe
,因为swift
指针是对内存直接操作,是不安全的。
与OC指针的对应关系
Swift
指针与OC
指针的对应关系如下:
Swift | OC | 说明 |
---|---|---|
unsafePointer<T> |
const T * |
指针及所指向的内存都不可变 |
unsafeMutablePointer |
T * |
指针及所指向的内存均可改变 |
unsafeRawPointer |
const void * |
指针指向未知类型 ,指向的内存不可变 |
unsafeMutableRawPointer |
void * |
指针指向未知类型 ,指向的内存可变 |
Swift指针的用法
- 在使用
Swift
指针时,内存管理需要手动管理,也就是创建的指针在最后需要调用deallocate
原始指针的使用
-
原始指针案例如下:
let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) // 创建指针 for i in 0..<4 { // 从pointer指针首地址处开始存,之后每平移8字节存储一个Int类型的数 pointer.advanced(by: i * 8).storeBytes(of: i+1, as: Int.self) } for i in 0..<4 { // 从pointer指针首地址开始读,之后每平移8字节读一次 let value = pointer.load(fromByteOffset: i * 8, as: Int.self) print("index : \(i), value : \(value)") } pointer.deallocate() // 手动销毁 复制代码
- 使用步骤如下:
- 调用
allocate
函数创建一个8字节
,并遵循8字节对齐
的指针pointer
- 在
pointer
指针首地址处开始存储,之后每平移8字节
存储一个数advanced(by:)
函数是存储位置,如果不设置,就会存在指针的首地址处
- 通过内存平移读取内存中的内容
- 调用
deallocate
函数对指针进行手动销毁
- 调用
- 使用步骤如下:
创建类型指针的方式
- 在
OC
中直接打印&+指针
即可获得指针地址,而Swift
中则不是,下面介绍两种方式来得到类型指针
withUnsafePointer(to:
-
withUnsafePointer(to:
方法创建指针如下: -
下面来查看下
withUnsafePointer(to:
的源码:@inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result 复制代码
- 该方法传入的参数
value
带有inout
标记,则传参时需要传入地址 - 然后参数
body
是一个闭包,它参数的类型的指针,且有返回值 - 最后就是
throws
将闭包body
的返回值抛出,然后rethrows
将返回值抛给withUnsafePointer
函数
- 该方法传入的参数
-
获取到指针后,可以使用
pointer.pointee
来打印指针指向的值 -
由于
withUnsafePointer
中的返回值Result
类型不是固定的,所以也可以这样写:- 此时
pointer
不是指针,而是int
类型的值,且num
值发生了改变
- 此时
-
throws
和rethrows
的使用:func getAge(_ num: Int, completed: (Int) -> String) -> String { let result = completed(num) return result } // 调用 let age = getAge(20) { "\($0)" } 复制代码
- 当闭包中有返回值且整个方法有返回值时,我们通常需要拿到闭包的返回值,再进行
retrun
给方法本身 - 而使用
throws
和rethrows
的话,只需将闭包结果抛出即可:
func getAge(_ num: Int, completed: (Int) throws -> String) rethrows -> String { try completed(num) } 复制代码
- 当闭包中有返回值且整个方法有返回值时,我们通常需要拿到闭包的返回值,再进行
-
throws
和rethrows
区别:-
throws
:表示这个函数可能会
抛出异常,在调用时需要在do-catch
中加上try
,如下例子func getAge(_ num: Int) throws -> String { return "\(num)" } do { let age = try getAge(20) } catch {} 复制代码
-
rethrows
:表示这个函数本身不会抛出
异常,但如果作为参数的闭包抛出
了异常,那么它会把异常继续
抛上去
-
allocate
-
类型指针还可以使用
allocate
来创建:let num = 20 let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1) ptr.initialize(to: num) ptr.deinitialize(count: 1) ptr.deallocate() 复制代码
- 创建代码的相关方法作用如下:
allocate(capacity:)
:根据类型创建内存,count
是要分配对应类型的内存量deallocate
:销毁内存,与allocate
成对出现initialize(to:)
:进行初始化
,to
是变量deinitialize(count:)
:deinitialize
是去初始化
,count
是去初始化
的实例数,不能为负数,initialize
与deinitialize
成对出现
- 创建代码的相关方法作用如下:
Swift指针的一些使用场景
结构体类型的指针使用
-
创建一个
WSPerson
结构体,然后创建两个类型指针:struct WSPerson { var name: String = "ws" var age: Int = 18 } let ptr = UnsafeMutablePointer<WSPerson>.allocate(capacity: 2) ptr.initialize(to: WSPerson()) // 在ptr内存首地址处初始化一个WSPerson实例 (ptr+1).initialize(to: WSPerson(name: "dj", age: 20)),在ptr首地址平移一个WSPerson内存单位处存储第二个WSPerson实例 ptr.deinitialize(count: 2) // 去初始化数量为2 ptr.deallocate() 复制代码
- 代码中主要通过指针内存平移来初始化两个
WSPerson
实例 (ptr+1)
是从ptr
首地址开始向下
平移WSPerson
内存,也可以这样写ptr.advanced(by: 1) // 这里平移1是因为已经知道类型,所以以此处的1代表一个`WSPerson`内存单位,与(ptr+1)是一样的 复制代码
- 访问
WSPerson
的实例可以用以下几种方法:-
ptr.pointee
,(ptr+1).pointee
-
ptr[0]
,ptr[1]
-
(ptr + 1).predecessor().pointee
,ptr.successor().pointee
-
predecessor
是上一个连续的WSPerson
大小的指针,successor
是下一个连续的WSPerson
大小的指针,与内存平移道理一样- 验证结果如下:
- 代码中主要通过指针内存平移来初始化两个
对象绑定到结构体类型
对象绑定到对象的结构体类型
-
有类
Wushuang
和结构体HeapObject
,代码如下:struct HeapObject { var kind: UnsafeRawPointer var strongref: UInt32 var unownedred: UInt32 } class Wushuang { var age: Int = 18 } 复制代码
- 在 Swift进阶-类&对象&属性 分析得出的
swift对象
的本质是HeapObject
,所以此处写一个结构类似的结构体用来绑定对象
- 在 Swift进阶-类&对象&属性 分析得出的
-
对象绑定结构体的具体代码如下:
var ws = Wushuang() let p = Unmanaged.passUnretained(ws as AnyObject).toOpaque() // 获取实例对象 let heapObj = p.bindMemory(to: HeapObject.self, capacity: 1) // 绑定到具体的类型 复制代码
Unmanaged.passUnretained
:获取实例对象ws
的指针Unmanaged
:是托管,类似于OC
中的__bridge
进行所有权转换passUnretained
:不需要所有权,只需要指针,得到的是UnsafeMutableRawPointer
指针
bindMemory
:绑定到具体的类型,内存大小为一个HeapObject
内存大小,此时得到结果就是HeapObject
类型的指针
-
使用相关打印验证如下:
对象中isa
绑定到类的结构体类型
-
我们知道
HeapObject
中的kind
实质就是isa
,它指向了类,但上面的例子类的结构类型还没有绑定。在 Swift进阶-类&对象&属性 中我们知道swift类
的结构,可以将它写成伪代码如下:struct swift_class_t { var isa: UnsafeRawPointer var superclass: UnsafeRawPointer var cacheData: UnsafeRawPointer var data: UnsafeRawPointer var flags: UInt32 var instanceAddressOffset: UInt32 var instanceSize: UInt32 var instanceAlignMask: UInt16 var reserved: UInt16 var classSize: UInt32 var classAddressOffset: UInt32 var description: UnsafeRawPointer } 复制代码
-
将
heapObj
的kind
绑定到swift_class_t
类型得到metadata
:let metadata = heapObj.pointee.kind.bindMemory(to: swift_class_t.self, capacity: 1) 复制代码
- 打印
metadata
结果如下:
- 打印
-
之所以能这样绑定,是因为本质上真是的
swift_class
结构,和此处伪代码的一样,所以不影响存取
元组类型指针的使用
-
代码如下:
var turple = (18, 22) withUnsafePointer(to: &turple) { ptr in print(ptr) } 复制代码
- 在查看
ptr
类型时发现是UnsafePointer<(Int, Int)>
类型,而指针是根据内存平移来访问值,也就是指针的类型应该是UnsafePointer<Int>
才能满足要求
- 在查看
-
这种情况需要使用
assumingMemoryBound(to:)
函数,具体代码如下var turple = (18, 22) withUnsafePointer(to: &turple) { ptr in testPointer(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self)) } func testPointer(_ t: UnsafePointer<Int>) { print(t) } 复制代码
- 由于
ptr
是已初始化指针,所以先将ptr
强转成UnsafeRawPointer
,然后去绑定类型,但此时不能使用bindMemory
,因为本质上ptr
已经绑定了类型,所以此时要使用assumingMemoryBound
假装绑定,强转后的新指针,假装去绑定Int
类型,进而将类型转成UnsafePointer<Int>
- 由于
-
打印结果如下:
获取结构体中变量的指针
- 核心代码与元组类型一样,需要使用
assumingMemoryBound
进行假设绑定. - 有两种方式获取结构体变量的指针:
获取首地址再平移
和直接获取结构体变量地址
,将两种方法放在一块对比:struct SHeapObject { var strongref: Int = 10 var unownedref: Int = 20 } func testPointer(_ t: UnsafePointer<Int>) { print(t) } var h = SHeapObject() withUnsafePointer(to: &h) { ptr in // 第一种 获取首地址,通过内存平移拿到unownedref地址 testPointer(UnsafeRawPointer(ptr).assumingMemoryBound(to: Int.self)) // 第二种,直接获取unownedref地址进行假装绑定 let unownedred = UnsafeRawPointer(ptr) + MemoryLayout<HeapObject>.offset(of: \HeapObject.strongref)! testPointer(unownedred.assumingMemoryBound(to: Int.self)) } 复制代码
offset(of:)
是变量相对的路径,需要带\
,打印结果如下:
- 结果二者得到的地址一样
临时更改内存绑定类型
-
当我们得到的
Int
类型指针时,使用时需要转成UInt64
型的指针,就需要用到withMemoryRebound(to: , capacity:)
方法来进行 临时更改绑定类型 ,具体代码如下:var age = 20 let ptr = withUnsafePointer(to: &age) { $0 } let uInt64Ptr = ptr.withMemoryRebound(to: UInt64.self, capacity: 1) { $0 } testPointer(uInt64Ptr) func testPointer(_ t: UnsafePointer<UInt64>) { print(t) } 复制代码
总结
-
Swift
中的指针分为两类,指定数据类型指针(typed pointer
) 和 未指定数据类型指针(raw pointer
)
-
- 创建指针 的方式有两种:
- 调用
withUnsafePointer(to:)
方法创建 - 通过
allocate
方法进行创建,但需要调用deallocate
进行销毁
-
- 绑定类型:
bindMemory
:将指针绑定到具体类型,用于未知类型指针去绑定类型assumingMemoryBound
: 假装绑定新的类型,用于某些特殊类型,例如获取结构体
某个变量的指针withMemoryRebound
:临时更改绑定类型,用于需要指针类型类型转成其他类型使用,例如Int
类型指针需要转成UInt64
使用