指针
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
类型的指针。
网友评论