Swift
中的指针分为两类:
① typed pointer 指定数据类型指针
,即 UnsafePointer
,其中T
表示泛型
;
②raw pointer 未指定数据类型的指针(原生指针
) ,即UnsafeRawPointer
。
与OC的指针
对比如下:
Swift | OC | 说明 |
---|---|---|
unsafePointer |
const T * | 指针及所指向的内容都不可变 |
unsafeMutablePointer | T * | 指针及其所指向的内存内容均可变 |
unsafeRawPointer | const void * | 指针指向未知类型,指向的值必须是常量 |
unsafeMutableRawPointer | void * | 指针指向未知类型可变 |
其中,T表示泛型
,对照上文,OC指针
表示,如:NSObjct *objc = [NSObjct alloc] init]
,void *objc = [NSObjct alloc] init]
。
一、type pointer
获取基本数据类型的地址可通过withUnsafePointer(to:)
方法获取,例如:
/**
value(T):
body(Result): 闭包表达式,通过rethrows重新抛出Result(即闭包表达式产生的结果)。
方法:
@inlinable public func withUnsafePointer
(to value: T, _ body: (Swift.UnsafePointer) throws -> Result)
rethrows -> Result {}
*/
var age = 18
let p = withUnsafePointer(to: &age) { ptr in
return ptr }
print(p)
//withUnsafePointer: 方法中的闭包属于单一表达式,因此可以省略参数、返回值,
//直接使用$0,$0等价于ptr,表示第一个参数,$1表示第二个参数
//简写为: withUnsafePointer(to: &age){print($0)}
//访问指针的值,可以直接通过:pointee属性
print(p.pointee)
p
的类型是 UnsafePointer
:
1.1 修改指针值
修改指针指向的值
,有2种方式,间接修改
& 直接修改
。
var age = 18
//1、间接修改
age = withUnsafePointer(to: &age) { p in
//返回Int整型值
return p.pointee + 10
}
print("1、间接修改:\(age)")
//2.1、直接修改
withUnsafeMutablePointer(to: &age) { p in
p.pointee += 10
}
print("2.1、直接修改:\(age)")
/**
2.2 直接修改,通过 allocate 创建 UnsafeMutablePointer
①,initialize 与 deinitialize是成对的
②,deinitialize中的count与申请时的capacity需要一致
③,需要deallocate
*/
//分配容量大小,为8字节
let p = UnsafeMutablePointer.allocate(capacity: 1)
//初始化
p.initialize(to: age)
p.deinitialize(count: 1)
p.pointee += 10
print("2.2、直接修改 p:\(p.pointee)")
print("2.2、直接修改 age:\(age)")
//释放
p.deallocate()
控制台打印:
1、间接修改:28
2.1、直接修改:38
2.2、直接修改 p:48
2.2、直接修改 age:38
问题:为什么最后两次打印的值不一样,因为指针修改之后,未赋值给age。
二、raw pointer
raw pointer
也叫做原生指针
,就是指未指定数据类型的指针。需要注意,raw pointer
需要手动管理
指针的内存
,所以指针在使用完需要手动释放
。
//定义一个未知类型的指针:本质是分配32字节大小的空间,指定对齐方式是8字节对齐
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
/**
存值1:读取数据时有问题,原因是因为读取时指定了每次读取的大小。
但是存储是直接在8字节的p中存储了i+1,即可以理解为并没有指定存储时的内存大小
*/
//for i in 0..<4 {
// p.storeBytes(of: i + 1, as: Int.self)
//}
//存值2
for i in 0..<4 {
//指定当前移动的步数,即i * 8
p.advanced(by: i * 8).storeBytes(of: i + 1, as: Int.self)
}
//取值
for i in 0..<4 {
//p是当前内存的首地址,通过内存平移来获取值
let value = p.load(fromByteOffset: i * 8, as: Int.self)
print("index: \(i), value: \(value)")
}
//使用完需要手动释放
p.deallocate()
三、指针应用
3.1 访问结构体
struct Animal{
var age: Int = 8
var height:Float = 1.75
}
然后,使用UnsafeMutablePointer
创建指针,访问结构体对象t
// 分配2个Animal大小的空间
let p = UnsafeMutablePointer.allocate(capacity: 2)
// 初始化第一个空间
p.initialize(to: Animal())
// 移动,初始化第2个空间
p.successor().initialize(to: Animal(age: 10, height: 1.55))
//异常:p.advanced(by: MemoryLayout.stride).initialize(to: Animal(age: 20, height: 1.80))
//正常:p.advanced(by: 1).initialize(to: Animal(age: 20, height: 1.80))
//访问指针
//方式1:下标访问
print(p[0])
print(p[1])
//方式2:内存平移
print(p.pointee)
print((p+1).pointee)
//方式3:successor()
print(p.pointee)
print(p.successor().pointee) //successor 往前移动
//必须和分配是一致的
p.deinitialize(count: 2)
//释放
p.deallocate()
注意:通过
advanced(by: MemoryLayout
赋值不行,但是.stride) advanced(by: 1)
可以,why?
advanced(by: MemoryLayout
的移动步长是.stride) 类Animal
实例的大小16字节
,而advanced(by: 1)
是移动步长为1
。
关键 在于这句代码let ptr = UnsafeMutablePointer
,此时我们是知道.allocate(capacity: 2) ptr的具体类型
的,就是指向Animal的指针
。
所以:在确定指针的类型后,通过步长的移动+1,就表示移动了那个类的实例大小空间+1。
3.2 实例对象绑定到struct内存
struct HeapObject {
var kind: Int //测试:Int, UnsafeRawPointer
var strongRef: UInt32
var unownedRef: UInt32
}
class Animal{
var age = 18
}
var t = Animal()
将 Animal实例
对象t绑定到结构体HeapObject
中:
//将t绑定到结构体内存中
/*
1、获取实例变量的内存地址,声明成了非托管对象
通过 Unmanaged 指定内存管理,类似于 OC 与 CF 的交互方式(所有权的转换 __bridge)
- passUnretained 不增加引用计数,即不需要获取所有权
- passRetained 增加引用计数,即需要获取所有权
- toOpaque 不透明的指针
*/
let p = Unmanaged.passUnretained(t as AnyObject).toOpaque()
/*
2、绑定到结构体内存,返回值是 UnsafeMutablePointer
- bindMemory 更改当前 UnsafeMutableRawPointer 的指针类型,绑定到具体的类型值
- 如果没有绑定,则绑定
- 如果已经绑定,则重定向到 HeapObject类型上
*/
let heapObject = p.bindMemory(to: HeapObject.self, capacity: 1)
//3、访问成员变量
//print(heapObject.pointee.kind)
print(heapObject.pointee)
这时打印的kind是数值
,把Kind的类型
改为 UnsafeRawPointer
,就可以打印地址
了。
3.2.1 绑定到类结构
Swift底层
对应的class结构
:
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 flinstanceAlignMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressOffset: UInt32
var description: UnsafeRawPointer
}
然后,把kind
绑定到类结构
:
//1、绑定到swift_class
let metaPtr = heapObject.pointee.kind.bindMemory(to: swift_class.self, capacity: 1)
//2、访问
print(metaPtr.pointee)
运行结果:
HeapObject(kind: 0x0000000100008190, strongRef: 3, unownedRef: 0)
swift_class(kind: 0x0000000100008158, superClass: 0x00000001f49631a8, cachedata1: 0x00000001893f0e60, cachedata2: 0x0000802000000000, data: 0x00000001005169c2, flags: 2, instanceAddressOffset: 0, instanceSize: 24, flinstanceAlignMask: 7, reserved: 0, classSize: 136, classAddressOffset: 16, description: 0x0000000100003c90)
3.3 元组指针类型转换
代码:
var tul = (10, 20)
//UnsafePointer
func testPointer(_ p : UnsafePointer){
print(p)
print("第一个值:\(p.pointee),第二个值:\((p+1).pointee)")
}
withUnsafePointer(to: &tul) { (tulPtr: UnsafePointer<(Int, Int)>) in
//不能使用bindMemory,因为已经绑定到具体的内存中了
//使用assumingMemoryBound,假定内存绑定,目的是告诉编译器ptr已经绑定过Int类型了,不需要再检查memory绑定
testPointer(UnsafeRawPointer(tulPtr).assumingMemoryBound(to: Int.self))
}
上面是将元组tul
的指针类型 UnsafePointer<(Int, Int)>)
转换成了UnsafePointer
。
也可以直接告诉编译器转换成具体的类型:
func testPointer(_ p: UnsafeRawPointer){
p.assumingMemoryBound(to: Int.self)
}
3.4 获取结构体的成员变量的指针
struct HeapObject {
var strongRef: UInt32 = 10
var unownedRef: UInt32 = 20
}
func testPointer(_ p: UnsafePointer){
print(p)
}
//实例化
var t = HeapObject()
//获取结构体属性的指针传入函数
withUnsafePointer(to: &t) { (ptr: UnsafePointer) in
//获取变量
let strongRef = UnsafeRawPointer(ptr) + MemoryLayout.offset(of: \HeapObject.strongRef)!
//传递strongRef属性的值
testPointer(strongRef.assumingMemoryBound(to: Int.self))
}
通过withUnsafePointer
将t
绑定到结构体HeapObject
内存中,然后通过 MemoryLayout
内存平移获取结构体成员变量strongRef
,最后通过assumingMemoryBound
进行内存的绑定。
assumingMemoryBound
:假定内存绑定,就是告诉编译器,我的类型就是这个,不用检查了,但实际类型不变。
3.5 临时绑定内存类型
var age = 10
func testPointer(_ p: UnsafePointer){
print(p)
}
//异常:参考
withUnsafePointer(to: &age) { (ptr: UnsafePointer) in
//Cannot convert value of type 'UnsafePointer' to expected argument type 'UnsafePointer'
//testPointer(ptr) //报错:指针类型不一致
}
let ptr = withUnsafePointer(to: &age) {$0}
//通过withMemoryRebound临时绑定内存类型
ptr.withMemoryRebound(to: Int64.self, capacity: 1) { (ptr: UnsafePointer) in
testPointer(ptr)
}
总结
withMemoryRebound
:临时更改内存绑定类型;
bindMemory(to: Capacity:)
:更改内存绑定的类型,如果之前没有绑定,那么就是首次绑定,如果绑定过了,会被重新绑定为该类型;
assumingMemoryBound
:假定内存绑定,就是告诉编译器,我的类型就是这个,不用检查了,其实际类型不变。