swift指针
你可以使用UnsafePointer
类型的实例对象,来访问内存中特定类型的数据。指针可以访问的数据类型是指针的Pointee
类型。UnsafePointer
不安全的指针没有提供自动的内存管理和对齐保障。你负责处理你使用的内存数据的生命周期,来避免内存泄漏和内存未定义.
手动管理的内存既可以是未定义的也可以是绑定到特定类型的。你使用UnsafePointer
类型来访问和管理已绑定特定类型的内存。
了解指针的内存状态
UnsafePointer
实例引用的内存可以是以下几种状态之一。
许多指针操作必须只能应用于具有处于特定状态的内存的指针。你必须跟踪你正在使用的内存的状态,并且了解不同操作对内存状态的改变。内存可以是无类型的并且未初始化的,绑定到类型并且未初始化的,或者绑定到类型并且已经初始化的。最后,先前分配的可能已经被释放掉了,只留下引用它的指针,可能现在已经指向了未分配的内存空间。
Uninitialized Memory 未初始化的内存
刚通过类型指针分配的内存或者已经取消初始化的内存处于未初始化状态。未初始化的内存在访问读取数据之前必须先被初始化。
Initialized Memory 已初始化的内存
已初始化的内存,拥有一个可以通过指针的
pointee
属性或者下标读取的值。
在下面的例子中。ptr是一个用数值23来初始化的内存指针。**
let ptr: UnsafePointer<Int> = ...
// ptr.pointee == 23
// ptr[0] == 23
访问不同类型的指针的内存
当你通过指针访问内存时,Pointee
的类型必须与内存的绑定类型一致。如果你确实需要访问被另一种数据类型绑定的内存,swift的指针类型提供了一种类型安全的方式来临时或永久改变内存的绑定类型,或者直接从原始内存中加载实例化的类型实例。
uint8Pointer是一个分配了8个字节的UnsafePointer指针类型实例,将在下面的例子中使用。
let uint8Pointer: UnsafePointer<UInt8> = fetchEightBytes()
当你只需要临时访问其他类型的指针的内存时,可以使用withMemoryRebound(to:capacity:)
方法。例如,你可以使用这个方法来调用一个API,该API需要一个指向与指针的Pointee
布局兼容的其他类型的指针。以下代码临时将uint8Pointer
引用的内存从UInt8绑定到Int8,以调用导入的C的strlen函数。
// Imported from C
func strlen(_ __s: UnsafePointer<Int8>!) -> UInt
let length = uint8Pointer.withMemoryRebound(to: Int8.self, capacity: 8) {
return strlen($0)
}
// length == 7
当你需要将内存永久绑定到其他类型时,请先获取指向内存的原始指针,然后调用
原始指针上的bindMemory(to:capacity:)
方法。 以下范例将uint8Pointer
所引用的内存绑定到的一个UInt64
类型的实例:
let uint64Pointer = UnsafeRawPointer(uint8Pointer)
.bindMemory(to: UInt64.self, capacity: 1)
将uint8Pointer
引用的内存重新绑定到UInt64
类型的指针以后,以UInt8
指针实例访问该指针的引用内存时,uint8Pointer
是未定义的。
var fullInteger = uint64Pointer.pointee // OK
var firstByte = uint8Pointer.pointee // undefined
或者,你可以访问与其他类型相同的内存,只要绑定类型和目标类型是琐碎的类型。 将您的指针转换为UnsafeRawPointer
实例,然后使用原始指针的load(fromByteOffset:as :)
方法读取值。
let rawPointer = UnsafeRawPointer(uint64Pointer)
fullInteger = rawPointer.load(as: UInt64.self) // OK
firstByte = rawPointer.load(as: UInt8.self) // OK
指针执行算术计算
跟C语言一样,也可以对指针进行算术操作。地址偏移的值为Pointee
类型的大小。
// 'intPointer' points to memory initialized with [10, 20, 30, 40]
let intPointer: UnsafePointer<Int> = ...
// Load the first value in memory
let x = intPointer.pointee
// x == 10
// Load the third value in memory
let offsetPointer = intPointer + 2
let y = offsetPointer.pointee
// y == 30
同样,也可以通过下标来访问内存地址中的值
let z = intPointer[2]
// z == 30
隐式转换和桥接
当调用带有UnsafePointer
类型的参数的方法使时,你可以传递该特定类型的指针的实例,或者一个兼容类型的指针实例,还可以使用swift的隐式桥接来传递一个兼容类型。
func printInt(atAddress p: UnsafePointer<Int>) {
print(p.pointee)
}
printInt(atAddress: intPointer)
// Prints "42"
因为可变类型的指针可以隐式转换为不可变指针,作为参数传递时具有相同Pointee
类型的指针。
所以还可以通过UnsafeMutablePointer
实例调用printInt(atAddress :)
。
let mutableIntPointer = UnsafeMutablePointer(mutating: intPointer)
printInt(atAddress: mutableIntPointer)
// Prints "42"
另外,你可以使用swift的隐式桥接来传递指针参数。下面示例传递了一个使用inout
语法来指向value变量的指针。
var value: Int = 23
printInt(atAddress: &value)
// Prints "23"
当你将一个数组作为参数传递时,会隐式创建一个指向该数组第一个元素的不可变指针。
let numbers = [5, 10, 15, 20]
printInt(atAddress: numbers)
// Prints "5"
你还可以使用inout语法,创建一个可变的指针,传递给printInt(atAddress:)
方法,这样语法上同样是有效的,但是没必要。
var mutableNumbers = numbers
printInt(atAddress: &mutableNumbers)
不论你以何种方式调用printInt(atAddress:)
方法,swift的类型安全保证你只能传递一种类型的指针给方法,那就是指向Int
数据类型的指针。
重要提示:通过隐式桥接创建实例或或数组元素的指针,仅在方法执行时有效。在函数执行后,逃逸指针是一种未定义的行为。 特别是不要在调用
UnsafePointer
初始化程序时使用隐式桥接。
var number = 5
let numberPointer = UnsafePointer<Int>(&number)
//访问'numberPointer'是未定义的行为。
```