谈内存必然离不开指针的概念,指针既是难点也是重点。
Swift中也有专门的指针类型,这些都被定性为Unsafe
(不安全的),常见的有以下4种类型:
UnsafePointer
:类似于const Pointee *
(只读的泛型指针)UnsafeMutablePointer
:类似于Pointee *
(可读可写的泛型指针)UnsafeRawPointer
:类似于const void *
(只读的原始类型指针)UnsafeMutableRawPointer
:类似于void *
(可读可写的原始类型指针)示例代码一:
var age = 10
func test1(_ ptr: UnsafeMutablePointer<Int>) {
ptr.pointee += 10
print("test1 \(ptr.pointee)")
}
test1(&age) // 输出:test1 20
func test2(_ ptr: UnsafePointer<Int>) {
print("test2 \(ptr.pointee)")
}
test2(&age) // 输出:test2 20
func test3(_ ptr: UnsafeMutableRawPointer) {
ptr.storeBytes(of: 30, as: Int.self)
print("test3 \(ptr.load(as: Int.self))")
}
test3(&age) // 输出:test3 30
func test4(_ ptr: UnsafeRawPointer) {
print("test4 \(ptr.load(as: Int.self))")
}
test4(&age) // 输出:test4 30
泛型指针可以通过指针变量属性pointee
读写内存。
原始指针通过load
实例方法读取内存数据,参数as
传入创建的实例类型。
原始指针通过storeBytes
实例方法写入数据,参数as
传入存储数据的类型,参数
of`传入数据。
示例代码:
var age = 10
func test(_ ptr: UnsafePointer<Int>) {
print(ptr.pointee)
}
test(&age)
调用函数的时候传参&age
,意味着传入的是指针地址,通过这个地址可以直接获取age
的内存。能不能直接定义变量的时候就定义呢?
直接定义一个指针变量指向了age
:
var ptr: UnsafePointer<Int> = &age
明显不行,编译器直接报错了:
可以使用下面的方法获取指针变量:
@inlinable public func withUnsafePointer<T, Result>(to value: inout T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
使用:
var ptr = withUnsafePointer(to: &age) {
$0 }
print(ptr.pointee) // 输出:10
withUnsafePointer
函数的第一个参数是传入变量的地址,第二个参数是闭包,闭包的参数其实是函数的第一个参数,返回值是一个泛型(传参是什么类型,返回值就是什么类型)。
模仿实现withUnsafePointer
代码:
func withUnsafePointer<Result, T>(to: UnsafePointer<T>, body: (UnsafePointer<T>) -> Result) -> Result {
body(to)
}
withUnsafePointer
和withUnsafeMutablePointer
返回的都是泛型指针,通过修改尾随闭包的返回值类型可以间接修改这两个函数的返回值类型。
UnsafeRawPointer
和UnsafeMutableRawPointer
都有各自的初始化器。
var ptr = withUnsafePointer(to: &age) {
UnsafeRawPointer($0) }
// ptr是UnsafeRawPointer类型
var ptr2 = withUnsafeMutablePointer(to: &age) {
UnsafeMutableRawPointer($0) }
// ptr2是UnsafeMutableRawPointer类型
证明获取的是指针:
var age = 10
var ptr = withUnsafePointer(to: &age) {
$0 }
print(ptr) // 输出:0x000000010000c340
通过计算得出:0x100001d2e + 0xa612 = 0x10000C340
,和上面指针变量ptr
打印的地址值一样,也就是age
保存的地址。
示例代码:
class Person {
var age: Int
init(age: Int) {
self.age = age
}
}
var person = Person(age: 10)
var ptr = withUnsafePointer(to: &person) {
$0 }
print(ptr.pointee.age) // 输出:10
思考:ptr
存储的是什么?存储的是变量person
的地址值还是堆空间Person
对象的地址值?
print(ptr) // 输出:0x000000010000c4c8
print(Mems.ptr(ofVal: &person)) // 输出:0x000000010000c4c8(person变量的地址值)
print(Mems.ptr(ofRef: person)) // 输出:0x00000001006302d0(堆空间Person对象的地址值)
很明显ptr
存储的是person
变量的地址值。其实从withUnsafePointer
的入参和返回值也能反映出ptr
存储的是person
变量的地址值,因为传入什么,返回值就是什么。ptr
本质就是person
。
获取堆空间的地址:
var person = Person(age: 10)
var ptr1 = withUnsafePointer(to: &person) {
UnsafeRawPointer($0) }
var personObjAddress = ptr1.load(as: UInt.self)
var ptr2 = UnsafeMutableRawPointer(bitPattern: personObjAddress)
print(ptr2) // 输出:Optional(0x0000000100657f60)
ptr1
保存的是person
地址值,所以ptr1.load
取的是person
保存的地址personObjAddress
(对象堆空间地址)。ptr2
指针装的就是对象堆空间地址,换句话说就是ptr2
指针指向了对象堆空间地址。
示例代码一(通过malloc
创建):
// 堆空间创建指针(16代表申请16个字节的内存,返回值类型是UnsafeMutableRawPointer可选类型)
var ptr = malloc(16)
// 存数据
// 指针前8个字节填充数据(Int类型数字10)
// of: 存放的数据
// as: 存放的数据类型
ptr?.storeBytes(of: 10, as: Int.self)
// 指针后8个字节填充数据(Int类型数字20)
// toByteOffset: 字节偏移量(偏移8代表是因为Int占用8个字节)
ptr?.storeBytes(of: 20, toByteOffset: 8, as: Int.self)
// 取数据
// 取出指针指向的内存前8个字节的数据
print((ptr?.load(as: Int.self))!) // 输出:10
// 取出指针指向的内存后8个字节的数据(参数同存数据)
print((ptr?.load(fromByteOffset: 8, as: Int.self))!) // 输出:20
// 销毁
free(ptr)
通过malloc
创建的指针一定要在结束后销毁。
示例代码二(通过UnsafeMutableRawPointer.allocate
创建):
// 创建指针(返回值是UnsafeMutableRawPointer类型)
// byteCount: 申请的字节大小
// alignment: 对齐数(一般写1就好)
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
// 存数据
// 存储前8个字节数据
ptr.storeBytes(of: 11, as: Int.self)
// advanced返回的是ptr偏移by字节的UnsafeMutableRawPointer类型的指针
// 意思是:ptr后8个字节存储Int类型22
ptr.advanced(by: 8).storeBytes(of: 22, as: Int.self)
// 取数据
print(ptr.load(as: Int.self)) // 输出:11
print(ptr.advanced(by: 8).load(as: Int.self)) // 输出:22
// 销毁
ptr.deallocate()
注意advanced
返回的是偏移指定字节后的指针。
示例代码三(通过UnsafeMutablePointer.allocate
创建):
// 创建指针
// capacity: 容量(3就是申请24字节的内存--因为Int占用8个字节,所以一共是24字节)
var ptr = UnsafeMutablePointer<Int>.allocate(capacity: 3)
// 存数据
// 第一种初始化方式:pointee: 操作的是前8个字节
//ptr.pointee = 10
// 第二种初始化方式:initialize: 用10去初始化内存空间(前8个字节)
ptr.initialize(to: 10)
// 第三种初始化方式:连续存储:连续count*Int个字节重复存储repeating(连续2块内存都存储数字10)
// count: 重复次数
// repeating: 重复数据
//ptr.initialize(repeating: 10, count: 2)
// 后继: 跳过8个字节,返回指针(跳过前8个字节的指针存储20)
ptr.successor().initialize(to: 20)
// 可以连续跳(连续跳过前面16个字节)
ptr.successor().successor().initialize(to: 30)
// 取数据
// 取数据方式一
// 前8个字节
print(ptr.pointee) // 输出:10
// 往下移动n*8个字节
print((ptr + 1).pointee) // 输出:20
print((ptr + 2).pointee) // 输出:30
// 取数据方式二
// 取出第n段(每段8个字节)的字节
print(ptr[0]) // 输出:10
print(ptr[1]) // 输出:20
print(ptr[2]) // 输出:30
// 反初始化
ptr.deinitialize(count: 3)
// 销毁
ptr.deallocate()
使用泛型指针initialize
初始化数据时,一定要使用deinitialize
反初始化(count
和initialize
次数要一致)。
注意:
UnsafeMutableRawPointer.allocate
因为没有指定类型,所以需要传入指定类型和字节。
UnsafeMutablePointer.allocate
是泛型指针,所以只需要告诉系统创建多大容量的内存。
示例代码四:
class Person {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
deinit {
print(name, "deinit")
}
}
var ptr = UnsafeMutablePointer<Person>.allocate(capacity: 3)
ptr.initialize(to: Person(age: 10, name: "Jack"))
(ptr + 1).initialize(to: Person(age: 11, name: "Rose"))
(ptr + 2).initialize(to: Person(age: 12, name: "Kate"))
print("1")
ptr.deinitialize(count: 3)
print("2")
ptr.deallocate()
print("3")
/*
输出:
1
Jack deinit
Rose deinit
Kate deinit
2
3
*/
如果上面的代码不写deinitialize
,就不会有deinit
输出;如果deinitialize(count: 2)
,第三个initialize
就不会释放;所以一定要及时正确的使用deinitialize
,否则会有内存泄漏。
建议:在函数内部创建指针时,把指针释放的代码放到
defer
函数体内。
示例代码:
// 创建指针
var ptr = UnsafeMutableRawPointer.allocate(byteCount: 16, alignment: 1)
// 把原始指针转换为泛型指针
// to: 转换目标类型
ptr.assumingMemoryBound(to: Int.self).pointee = 11
(ptr + 8).assumingMemoryBound(to: Double.self).pointee = 22.0
// unsafeBitCast强制转换
// 第一个参数:待转换的指针
// 第二个参数:转换目标指针类型
print(unsafeBitCast(ptr, to: UnsafePointer<Int>.self).pointee) // 输出:11
print(unsafeBitCast(ptr + 8, to: UnsafePointer<Double>.self).pointee) // 输出:22.0
ptr.deallocate()
unsafeBitCast
是忽略数据类型的强制转换,不会因为数据类型的变化而改变原来的内存数据(可以认为是内存数据直接搬过去的,一般情况下的强制转换都会改变原来的内存数据形成新的内存数据存储)。
unsafeBitCast
也可以直接创建一个指针指向堆空间:
class Person {
}
var person = Person()
print(Mems.ptr(ofRef: person)) // 输出:0x0000000100556d10
var ptr = unsafeBitCast(person, to: UnsafeRawPointer.self)
print(ptr) // 输出:0x0000000100556d10
person
保存的是Person
对象的堆空间地址,unsafeBitCast
就是把person
保存的内存地址拿出来原封不动转换为UnsafeRawPointer
类型的指针给ptr
,所以ptr
保存的地址和person
保存的地址是一样的。
还有一种方式是把person
地址取出来,利用地址创建一个指针指向堆空间:
var address = unsafeBitCast(person, to: UInt.self)
var ptr = UnsafeRawPointer(bitPattern: address)
注意:原始指针和泛型指针
ptr + 8
是有区别的。原始指针ptr + 8
指的是跳过8个字节,泛型指针指的是跳过8*类型占用字节
个字节。