指针
Swift中指针分为两类:
-
typed pointer:指定数据类型指针,UnsafePointer<T>,T表示泛型。 -
raw pointer:未指定数据类型指针(原生指针),UnsafeRawPointer
Swift 中的指针和 OC 中指针的对应关系如下:

-
Raw Pointer的使用
注意: 指针的内存管理需要手动管理,使用完后需要手动释放。
例:使用Raw Pointer在内存中连续存储4个整型数据
// 以8字节对齐,分配32字节大小的内存空间
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
// 存储值到内存
for i in 0..<4 {
// 通过advanced(by:)指定存储时的步长
p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
}
// 从内存中读取值
for i in 0..<4 {
// 通过内存平移来读取内存中的值
let value = p.load(fromByteOffset:i * 8, as: Int.self)
print("index\(i), value:\(value)")
}
// 指针的内存管理需要手动管理
p.deallocate()
-
Type Pointer的使用
withunsafePointer(to:)
首先了解Swift中$0、$1的含义:
Swift自动为闭包提供参数名缩写功能,可以直接通过$0和 $1 来表示闭包中的第一个、第二个参数,并且对应的参数类型会根据函数类型来进行判断。
在前面,我们获取一个变量本身的地址都是通过 withUnsafePointer(to:) 的方式获取。
其函数定义如下:
@inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
可看到:
- 第一个参数是通过
inout修饰的泛型,前面说过inout用来修饰的参数表示传递的是地址 - 第二个参数是一个
闭包表达式,通过rethrows重新抛给Result(即闭包表达式产生的结果),所以可以通过简写闭包的参数和返回值,简写如下:
var age : Int = 10
// 第一种:单一表达式:相当于将ptr赋值给p,p的类型UnsafePointer<Int>
let p = withUnsafePointer(to: &age) { ptr in
return ptr
}
// 第二种:此时p为print($0)的返回结果,类型为print方法的返回类型Void
let p = withUnsafePointer(to: &age) { print($0) }
// 第三种:$0赋值给p,p的类型UnsafePointer<Int>
let p = withUnsafePointer(to: &age) { $0 }
从上可知:withUnsafePointer 这个函数result 类型取决于其参数闭包表达式的返回值,第一种和第三种得到的 p 都是UnsafePointer<Int>类型,可以通过 p.pointee 获取 age 的值。
还可以使用 withUnsafePointer 这个函数修改 age 的值:
age = withUnsafePointer(to: &age, { ptr in
return ptr.pointee + 12
})
-
withUnsafeMutablePointer(to:)
如果想在闭包表达式中直接修改age的值则需要用到withUnsafeMutablePointer:
withUnsafeMutablePointer(to: &age, { ptr in
ptr.pointee = 122
})
执行 上面代码,age 的值就直接被修改为 122 了。
UnsafeMutablePointer
// 分配容量为1的内存空间(Int8字节)
let ptr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
// 初始化
ptr.initialize(to: 22)
// 手动管理内存:对应initialize
ptr.deinitialize(count: 1)
// 手动管理内存:对应allocate
ptr.deallocate()
这时 ptr.pointee 读出来则为22。
指针实例演示
实例1:访问结构体指针
struct YYTeacher {
var age = 12
var name = "YY"
}
var t = YYTeacher()
// 分配装2个struct的内存空间
let ptr = UnsafeMutablePointer<YYTeacher>.allocate(capacity: 2)
// 初始化第一个struct的内存空间
ptr.initialize(to: YYTeacher())
// 第一种方式:初始化第二个struct的内存空间
ptr.successor().initialize(to: YYTeacher(age: 22, name: "EE"))
// 第二种方式:初始化第二个struct的内存空间
//(ptr + 1).initialize(to: YYTeacher(age: 22, name: "EE"))
// 第三种方式:初始化第二个struct的内存空间
//ptr.advanced(by: 1).initialize(to:YYTeacher(age: 22, name: "EE"))
// 两种方式获取第一个struct的值
print(ptr[0])
print(ptr.pointee)
// 三种方式获取第二个struct的值
print(ptr[1])
print((ptr + 1).pointee)
print(ptr.successor().pointee)
print(ptr.advanced(by: 1).pointee)
// 手动管理内存:对应initialize
ptr.deinitialize(count: 2)
// 手动管理内存:对应allocate
ptr.deallocate()
通过查看源码可知:successor() 本质就是 advanced(by:1)

对比上面 Raw Pointer 例子中的p.advanced(by: i * 8) ,因为上面的 p 是未知类型,要存储Int类型的值就必须每次指定移动 8 的倍数,这里的8是一个Int类型所占的字节数;而在 UnsafeMutablePointer 中初始化第二个struct内存空间时使用 ptr.advanced(by:1) ,这里已知 ptr 是结构体指针,只需要告知往前移动几步就可以了,这里的 1 相当于往前移动 1个struct步长 (这里的 struct 步长为24)就好,两个 struct 相差的字节大小为1 * 24。
实例2:将实例对象绑定到结构体内存中
struct HeapObject {
var kind: Int
var strongRef: UInt32
var unownedRef: UInt32
}
class YYTeacher {
var age = 20
}
var t = YYTeacher()
首先:获取实例对象的值
其次:将 raw pointer 绑定到具体类型指针
// 获取实例对象的值,返回值类型为 UnsafeMutableRawPointer
let ptr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
// 将当前Raw Pointer-->ptr重新绑定到HeapObject结构体,返回值类型为 UnsafeMutablePointer<HeapObject>
let heapObject = ptr.bindMemory(to: HeapObject.self, capacity: 1)
// 访问内存中的值
print(heapObject.pointee)
-
Unmanaged取代手动管理内存,用来托管指针,可以指定内存管理,类似于OC和CF交互时的CoreFoundation(__brige,有所有权的转换),在这里将实例对象t声明成了非托管对象给到ptr -
passRetained:增加引用计数,需要获取所有权,如OC中的create、copy关键字在声明过程中就需要所有权 -
passUnretained:不增加引用计数,不需要获取其所有权,不再需要手动去释放如CF中的CFRelese,在这里只需要获得其指针,不需要获取其所有权 -
bindMemory:
如果未绑定,则绑定
如果已绑定,则重定向到指定类型内存 - 如果这里将
var kind: Int改成var kind: UnsafeRawPointer,在print(heapObject.pointee.kind)时打印的则是地址
扩展:如何将 heapObject 的属性 kind 绑定到 Swift 类结构的结构体内存中?(此时的 kind 是 UnsafeRawPointer 类型)
- 首先定义个
swift类结构的结构体:
struct swift_class {
var kind: UnsafeRawPointer
var superClass: UnsafeRawPointer
var cacheData1: UnsafeRawPointer
var cacheData2: 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
}
- 将
kind绑定到swift_class这个结构体的内存中
// 绑定内存
let metaptr = heapObject.pointee.kind.bindMemory(to: swift_class.self, capacity: 1)
// 访问内存中的值
print(metaptr.pointee)
运行结果如下图:

其本质是因为 metaptr 和 swift_class 的内存结构是一样的。
实例3:元祖指针类型转换
如何将元祖 tul 的指针传给 testPointer 函数?
var tul = (10,20)
func testPointer(_ p: UnsafePointer<(Int)>) {
print(p)
}
在上面例子中,testPointer 函数的参数是一个UnsafePointer<(Int)> 类型的,而 tul 的指针类型为 UnsafePointer<(Int, Int)> ,这时就需要将 tul 的指针类型重新绑定内存。如下:
withUnsafePointer(to: &tul) {(tulPtr : UnsafePointer<(Int, Int)>) in
// 这里不能使用bindMemory,因为tulPtr已经绑定到具体类型了
// assumingMemoryBound:假定内存绑定,告诉编译器tulPtr已经绑定过Int类型了,不需要再检查memory绑定
testPointer(UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
}
那么:bindMemory(to:capacity:)和assumingMemoryBound(to:)以及withMemoryRebound(to:capacity:_:)用法有什么区别呢?
bindMemory(to:capacity:):如果以前未绑定,调用后则首次绑定到指定类型的内存;如果以前已绑定,调用后则重新绑定到指定类型(这里编译器会检查之前绑定的类型和现在指定的类型是否布局兼容)。assumingMemoryBound(to:):假定内存绑定,重新绑定到指定类型的内存,告诉编译器不用检查类型(绕过编译器检查类型)withMemoryRebound(to:capacity:_:):临时更改内存绑定类型。适用于已初始化的类型指针;使用此方法时,重新绑定的类型要与之前的指针类型具有相同 size和stride。
var age = 12
func testPointer(_ p: UnsafePointer<UInt64>) {
print(p)
}
let ptr = withUnsafePointer(to: &age){$0}
ptr.withMemoryRebound(to: UInt64.self, capacity: 1) {
testPointer($0)
}
在这里 ptr 本身是 Int 类型的指针,使用 withMemoryRebound 则临时将其指针类型绑定到 UInt64 类型的内存,使用结束后,其类型重新绑定成 Int 类型的指针。