Swift内存布局与指针操作
在讲解指针之前,我们先来了解一下内存布局,计算机中的内存都是以字节为单位存储的,但是大部分处理器并不是按字节块来存取内存的。它一般会以双字节,四字节,8字节,16字节甚至32字节为单位来存取内存,我们将上述这些存取单位称为内存存取粒度。所以我们需要通过一种内存对齐方式来存取,这样一方面可以节省内存,另一方面可以快速的取出对应类型的内存地址,为什么这么说,看完下面的内容,你就明白了。
我们知道计算机内存中的存储单位是字节,我们以Int32(占4字节)和UInt8(占1字节)为例,它们的内存结构如下:
接下来我们声明一个结构体,来详细的了解一下其在内存中的分配情况
struct MyStruct { let a: UInt8 = 0 let b: Int32 = 0 }
该结构体内部声明了一个UInt8和Int32类型的属性,那么其在内存中的布局如下:
接下来我们在讲其在内存地址中,详细的一段连续的内存地址的字节布局情况前,我们先说说对齐方式的规则,一个结构体中的成员中,以占用字节数最大的那个值作为该结构体MyStruct中内存对齐大小。因为UInt8占1字节,Int32占4字节,因此MyStruct的对齐值为4。接下来我们就看一下该结构体在内存中详细的字节分布情况
以上就是MyStruct在内存中的详细分布情况。其内存对齐值alignment:4,所占内存大小size:8,步长stride(当存储在连续内存或 Array<T> 中时,从 T 的一个实例的开始到下一个实例的开始的字节数):8。
注意:其所占内存大小是指连续的一段内存空间,UInt8占1字节,Int32占4字节,又因为存在对齐方式,因此连续内存空间大小为:8
接下来我们调整一下MyStruct中的属性声明顺序,让我们再来分析一下其内存分布情况
struct MyStruct { let b: Int32 = 0 let a: UInt8 = 0 }
其内存中的详细布局如下:
其内存对齐值alignment:4,所占内存大小size:5,步长stride(当存储在连续内存或 Array<T> 中时,从 T 的一个实例的开始到下一个实例的开始的字节数):8。
注意:其所占内存大小是指连续的一段内存空间,Int32占4字节,UInt8占1字节,因此连续内存空间大小为:5
接下来我们再来看看对齐方式是如何进行的,我们在以上结构体中再添加一个属性,其声明顺序如下:
struct MyStruct { let b: Int32 = 0 let a: UInt8 = 0 let c: UInt8 = 0 }
内存布局如下:
其内存对齐值alignment:4,所占内存大小size:6,步长stride(当存储在连续内存或 Array<T> 中时,从 T 的一个实例的开始到下一个实例的开始的字节数):8。
调换一下属性声明顺序:
struct MyStruct { let a: UInt8 = 0 let c: UInt8 = 0 let b: Int32 = 0 }
内存布局如下:
其内存对齐值alignment:4,所占内存大小size:8,步长stride(当存储在连续内存或 Array<T> 中时,从 T 的一个实例的开始到下一个实例的开始的字节数):8。
再次调换顺序:
struct MyStruct { let a: UInt8 = 0 let b: Int32 = 0 let c: UInt8 = 0 }
内存布局如下:
其内存对齐值alignment:4,所占内存大小size:9,步长stride(当存储在连续内存或 Array<T> 中时,从 T 的一个实例的开始到下一个实例的开始的字节数):12。
经过以上的调整顺序,我们大该了解了其在内存中的布局情况是如何通过对齐方式进行布局的了。
因此我们对内存的对齐方式做个总结,元素是按照定义顺序一个一个放到内存中去的,但并不是紧密排列的。从结构体存储的首地址开始,每个元素放置到内存中时,它都会认为内存是按照自己的大小(通常它为1或4)来划分的,因此元素放置的位置一定会在自己宽度的整数倍上开始,这就是所谓的内存对齐。根据上面事例也可以看出,当我们声明一个结构体时,其内部声明属性的次序决定了其在内存中所占空间的大小,因此根据对齐规则,我们可以减少声明的结构体的内存大小,并且在获取结构体实例时,也可以根据stride(步长)来快速获取。
在swift中获取一个类型的内存布局情况可以通过MemoryLayout<T>获取,它包括如下静态属性:
alignment(对齐数值),size(实际所占内存大小),stride(内存步长)。具体调用方式如下:
let alignment = MemoryLayout<MyStruct>.alignment let size = MemoryLayout<MyStruct>.size let stride = MemoryLayout<MyStruct>.stride
需要注意的是,MemoryLayout<class类型>获取的内存布局是这个类的内存布局,而不包括其内部属性,因为class是引用类型。例如:
class MyClass { let b: Int32 let a: UInt8 init() { a = 0 b = 0 } }
MyClass的内存对齐值alignment:8而不是4,所占内存大小size:8而不是5,步长stride:8。可以看出获取class类型的内存布局,与其内部的属性无关,而是这个class所在的内存布局。
讲完了内存布局,接下来开始讲讲指针操作了,学过C的都知道如何操作指针,在C语言中存在针对某一类型的指针例如int *a(一个int类型的指针),还有一种是通用类型指针void *(它可表示任何类型的指针),那么在swift中同样存在针对这两种指针的操作对象。
UnsafePointer<Pointee>(某一类型的指针,通过泛型指定类型),UnsafeRawPointer(无类型的指针,也叫原始指针,对应C语言中的void *),那么针对这两种中的每一种,还可以再细分,具体分类如下:
1、类型指针:
UnsafePointer<Pointee>(不可变类型指针),对应C语言指针为:const char * a
UnsafeMutablePointer<Pointee>(可变类型指针),对应C语言指针为:char * a
UnsafeBufferPointer<Element>(不可变的数组指针,即:相同类型元素的集合指针),针对C语言为:const int a[10]
UnsafeMutableBufferPointer<Element>(可变的数组指针),针对C语言为:int a[10]
2、通用类型指针:
UnsafeRawPointer(不可变通用指针),针对C语言指针为:const void * a
UnsafeMutableRawPointer(可变的通用指针),针对C语言指针为:void * a
UnsafeRawBufferPointer(不可变数组通用类型指针),针对C语言为:const void * a[10]
UnsafeMutableRawBufferPointer(可变数组通用类型指针),对应C语言为:void * a[10]
3、不透明指针
OpaquePointer:不透明指针用于表示C指针,指向不能在Swift中表示的类型,例如不完整结构类型。我们可以根据一段内存地址创建这个不透明指针对象,例如:OpaquePointer(bitPattern: 0x600001fafac0)
4、作为指针参数的指针
AutoreleasingUnsafeMutablePointer:用于swift中的inout类型参数实现,即:被inout标记的参数,会被转成该类型指针
指针操作对象的初始化
1、根据已有变量,获取其指针操作对象
var a = 10 // 获取a的不可变指针操作对象,并返回其值+1后的结果 let res = withUnsafePointer(to: &a) { (pointer: UnsafePointer<Int>) -> Int in // 因为pointer是不可变类型指针,并且可以看到pointer.pointee是只读的,因此我们无法改变该指针的值 return pointer.pointee + 1 // pointee取值操作,相当于C语言的,p[0] or *p } print("res: \(res)") // 11
可以看一下这个withUnsafePointer函数的定义:
public func withUnsafePointer<T, Result>(to value: T, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
从定义可以看出,该函数是通过一个闭包,将其指针操作对象返回给调用者的,并且这个闭包是可以抛出异常的,这样允许我们在闭包中处理异常错误时,抛出这个错误。当然你也可以定义一个不抛出异常的闭包函数,因为swift中的不抛出异常函数是抛出异常函数的子类,因此这样的操作是允许的,就像我们上面的例子一样。
我们还可以获取一个可变指针,用于修改它的值:
var a = 10 // 获取a的可变指针操作对象,使其值+1,并返回该指针+1之前的值 let res = withUnsafeMutablePointer(to: &a) { (pointer: UnsafeMutablePointer<Int>) -> Int in let oldV = pointer.pointee // pointee取值操作,相当于C语言的,p[0] or *p pointer.pointee += 1 return oldV } print("res: \(res), a: \(a)") // res: 10, a: 11
获取数组指针:
let list = [1, 2, 3, 4] let res = list.withUnsafeBufferPointer { (p: UnsafeBufferPointer<Int>) -> String in return p.map{"\($0)"}.joined(separator: ",") } print("res: \(res)") // res: 1,2,3,4
你还可以通过以下方法获取数组指针
var list = [1, 3, 4] // 获取给定参数的原始字节的缓冲区指针 let res = withUnsafeBytes(of: &list) { (p: UnsafeRawBufferPointer) -> String in // 将原始字节,转成指定类型 let array = p.load(as: [Int].self) return array.map{"\($0)"}.joined(separator: ",") } print("res: \(res)") // 1,3,4
获取一个可变的数组指针
var list = [1, 3, 4] withUnsafeMutablePointer(to: &list) { (p: UnsafeMutablePointer<[Int]>) in p.pointee.append(5) p.pointee.append(6) } print("list: \(list)") // list: [1, 3, 4, 5, 6]
2、直接分配内存指针(仅适用可变指针,因为内存分配后,需要初始化值,而不可变的指针,不能操作值)
分配一块儿指定类型的存储空间
let count = 3 // 分配一块内存空间,该内存空间标记为Int类型,并且开辟了3块儿Int类型的存储空间 let pointer = UnsafeMutablePointer<Int>.allocate(capacity: count) pointer.initialize(to: 10) // 给第一块儿内存初始化 pointer.successor().pointee = 12 // 为第二块儿内存初始化 (pointer + 2).initialize(to: 20) // 为第三块儿内存初始化 print("val1: \(pointer.pointee)") // 取第一块儿内存值 10 print("val2: \(pointer.successor().pointee)") // 取第二块儿内存值 12 print("val3: \((pointer + 2).pointee)") // 取第三块儿内存值 20 pointer.deallocate() // 在不需要这块儿内存时,需要我们手动释放
分配一块儿不指定类型的存储空间
struct People: CustomStringConvertible { let name: String let age: Int8 var description: String{ "name: \(name), age: \(age)" } } let count = 3 let pointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout<People>.stride * count, // 注意:这里必须是类型内存步长的整数倍数 alignment: MemoryLayout<People>.alignment) let peopleListPoint = pointer.bindMemory(to: People.self, capacity: count) // 为这块内存标记类型,这样在下面为内存赋值时,就可以直接用该类型的值进行初始化 // peopleListPoint变成了UnsafeMutablePointer<People>类型 peopleListPoint.initialize(to: People(name: "drbox", age: 24)) // 初始化第一块儿内存值 peopleListPoint.successor().initialize(to: People(name: "boss", age: 30)) // 初始化第二块儿内存值 (peopleListPoint + 2).initialize(to: People(name: "jack", age: 40)) // 初始化第三块儿内存值 for i in 0..<count { let people = peopleListPoint[i] print(people) } // 这块儿内存不用了,需要手动释放掉 peopleListPoint.deinitialize(count: count) peopleListPoint.deallocate()
你也可以通过如下方式分配一块儿内存
let ptr = malloc(24) // 分配一块儿容量为24字节的内存空间,并返回该空间地址的指针,指针类型为:UnsafeMutableRawPointer? ptr?.storeBytes(of: 100, as: Int.self) // 在前8位(Int类型为8位),存储一个100的值 ptr?.storeBytes(of: 200, toByteOffset: MemoryLayout<Int>.stride, as: Int.self) // 在8到16位的内存地址,存储一个200 ptr?.storeBytes(of: 300, toByteOffset: MemoryLayout<Int>.stride * 2, as: Int.self) // 在16到24位的内存地址,存储一个300 // 取值 if let v = ptr?.load(as: Int.self) { print("前8位存储的值:\(v)") // 前8位存储的值:100 } if let v = ptr?.load(fromByteOffset: MemoryLayout<Int>.stride, as: Int.self) { print("前8至16位存储的值:\(v)") // 前8至16位存储的值:200 } if let v = ptr?.load(fromByteOffset: MemoryLayout<Int>.stride * 2, as: Int.self) { print("前16至24位存储的值:\(v)") // 前16至24位存储的值:300 } // 内存不需要时,释放内存 ptr?.deallocate()
从无类型指针(原始指针)中获取对应位置的内存值
var a = (1, "a", 3) // 内存地址布局:Int、String、Int。转成字节:(8字节)xxxxxxxx、(16字节)xxxxxxxxxxxxxxxx、(8字节)xxxxxxxx withUnsafePointer(to: &a) { (p: UnsafePointer<(Int, String, Int)>) in // 将类型指针转成原始指针 let rawPointer = UnsafeRawPointer(p) // 这里我们可以通过原始指针的load方法来取对应位置的内存中的值,例如:rawPointer内存中的前8个字节为Int类型,因此可以如下获取整个值: let intVal1 = rawPointer.load(as: Int.self) // 注意,这里不要写错类型,如果这里指定的类型与内存中实际绑定的类型不匹配,将导致crash // 获取该指针首地址的值,我们还可以将首地址转成一个指定类型的指针,来获取其指针的值 let intPointer = rawPointer.assumingMemoryBound(to: Int.self) // intPointer为UnsafePointer<Int>类型 print("intPointer.val: \(intPointer.pointee)") // intPointer.val: 1 // 在获取内存中的下一个值时,我们需要移动当前指针的首地址,到下一个值的位置,load的另一个方法允许我们获取从当前指针首地址,偏移指定步长的内存值 // 这我们通过MemoryLayout<Int>.stride来获取当前指针首地址内存类型的步长,因此下面方法获取的内存为8-32字节的内存 let strVal = rawPointer.load(fromByteOffset: MemoryLayout<Int>.stride, as: String.self) // 当然我们也可以通过移动指针来实现,原始指针为我们提供了移动指针的方法,使其返回一个移动后的新指针 let newPointer = rawPointer.advanced(by: MemoryLayout<Int>.stride) // 这里指定移动8字节步长,得到的是指向8-32字节的新的原始指针 let str2Val = newPointer.load(as: String.self) // 这样我们就可以直接取该指针的首地址,并转成对应类型的值 // 接下来获取内存中最后一个值,该值为Int类型,因此我们可以在这个newPointer指针的基础上获取该值 let intVal2 = newPointer.load(fromByteOffset: MemoryLayout<String>.stride, as: Int.self) print("intVal1: \(intVal1)") // intVal1: 1 print("strVal: \(strVal)") // strVal: a print("str2Val: \(str2Val)") // str2Val: a print("intVal2: \(intVal2)") // intVal2: 3 }
取结构体类型的内存指针的对应属性值
struct People: CustomStringConvertible { let name: String let age: Int8 var description: String{ "name: \(name), age: \(age)" } } var people = People(name: "drbox", age: 24) withUnsafePointer(to: &people) { (p: UnsafePointer<People>) in //将类型指针,转成原始指针 let rawPointer = UnsafeRawPointer(p) let name = rawPointer.load(as: String.self) print("name: \(name)") // name: drbox // 通过内存布局对象,来获取结构体内,对应位置的属性在内存地址中的偏移量 if let offset = MemoryLayout<People>.offset(of: \People.age){ let age = rawPointer.load(fromByteOffset: offset, as: Int8.self) print("age: \(age)") // age: 24 // 除了load方式取值,我们还可以通过移动指针来取值,移动指针除了advanced(by:),我们还可以通过+操作符来移动原始指针,并返回一个新的原始指针,这就类似于C语言中的+一样 let newPointer = rawPointer + offset let age2 = newPointer.assumingMemoryBound(to: Int8.self).pointee print("age2: \(age2)") // age: 24 } }
内存类型绑定,也可以认为是类型转换,包括以下几个函数:
func assumingMemoryBound<T>(to: T.Type) -> UnsafeMutablePointer<T> func bindMemory<T>(to type: T.Type, capacity count: Int) -> UnsafeMutablePointer<T> func withMemoryRebound<T, Result>(to type: T.Type, capacity count: Int, _ body: (UnsafePointer<T>) throws -> Result) rethrows -> Result
assmuingMemoryBound(to:):极少数情况下,代码没有保留类型指针,只有原始指针,但明确知道内存绑定的类型,这时候就需要assmuingMemoryBound(to:)
来告诉编译器预期类型,转换成对应的类型指针(注意:这里的转换只是让编译器绕过类型检查,并没有实际发生转换)。
// 初始化一个原始指针(C:void *) let pointer = UnsafeMutableRawPointer.allocate(byteCount: 8, alignment: 8) // 向其对应内存空间中存储一个Int类型的值 pointer.initializeMemory(as: Int.self, repeating: 10, count: 1) // 此时如果我们需要操作这块儿内存空间时,需要使用其对应类型的指针,我们可以通过以下方法转换到指定类型指针 let int8Pointer = pointer.assumingMemoryBound(to: Int.self) // int8Pointer即为UnsafeMutablePointer<Int>类型 print("int8Value: \(int8Pointer.pointee)") // int8Value: 10 // 在调用以上函数进行指针类型转换时,前提是我们明确知道其原始指针内存类型,否则我们在转换一个与原始内存类型不同的指针类型时,会出现不可预料的错误 let strPointer = pointer.assumingMemoryBound(to: String.self) print("strValue: \(strPointer.pointee)") // 这里将原始指针内存类型为Int,转成String类型指针时,strPointer会转换失败,调用strPointer.pointee时可能会导致crash
bindMemory(to:capacity:):将内存绑定到指定类型,并返回指向绑定内存的类型化指针
。如果内存还没有类型绑定,则将首次绑定为该类型。如果内存已经进行类型绑定,则将重新绑定为该类型,并且内存里所有值都会变成该类型。
let count = 4 // 初始化一个以Int8对齐值为对齐标准值(1字节),分配100个字节的空间 let bytesPointer = UnsafeMutableRawPointer.allocate(byteCount: 100, alignment: MemoryLayout<Int8>.alignment) // 将前4个字节绑定为Int8类型 let int8Pointer = bytesPointer.bindMemory(to: Int8.self, capacity: count) int8Pointer.pointee = 10 // 首字节初始值 int8Pointer.successor().pointee = 20 // 第二个字节初始值 int8Pointer.successor().successor().pointee = 30 // 第三个字节初始值 int8Pointer.successor().successor().successor().pointee = 40 // 第四个字节初始值 // 因为bytesPointer的对齐值为1,即内存跨度为1,因此上面的初始化操作后,第一个字节存储了10,第二个字节存储了20,第三个字节存储了30,第四个字节存储了40 // 取出第一个字节存储的值 let int8Val1 = bytesPointer.load(as: Int8.self) print("int8Val1: \(int8Val1)") // int8Val1: 10 // 取出第二个字节存储的值 let int8Val2 = bytesPointer.load(fromByteOffset: MemoryLayout<Int8>.stride, as: Int8.self) print("int8Val2: \(int8Val2)") // int8Val2: 20 // 取出第三个字节存储的值 let int8Val3 = bytesPointer.load(fromByteOffset: MemoryLayout<Int8>.stride * 2, as: Int8.self) print("int8Val3: \(int8Val3)") // int8Val3: 30 // 取出第四个字节存储的值 let int8Val4 = bytesPointer.load(fromByteOffset: MemoryLayout<Int8>.stride * 3, as: Int8.self) print("int8Val4: \(int8Val4)") // int8Val4: 40 // 内存不用时,记得回收 bytesPointer.deallocate()
withMemoryRebound(to:capacity:body)
:临时更改内存绑定的类型,它的作用域只在闭包内。闭包返回时,将会重新绑定为原始类型。这可以将临时类型指针的访问和其他代码的作用域分开。但是它有一些严格的限制:
- 需要有原始指针
- 转换的类型和原始的类型需要有相同的跨度,即步长
var a: Int8 = 100 withUnsafePointer(to: &a) { (p: UnsafePointer<Int8>) in // 将Int8类型的指针,临时转成UInt8类型指针,因为Int8与UInt8有着相同的跨度,因此这个转换是被允许的 p.withMemoryRebound(to: UInt8.self, capacity: 1) { (ptr: UnsafePointer<UInt8>) in print("int8 convert to uint8: \(ptr.pointee)") } }
以上是对指针的基本操作,接下来我们结合ipv4报头来举例如何将Struct的ipv4报头格式,转成Data二进制,以及如何从Data二进制转成Struct格式的ipv4报头,这个例子一般涉及到底层逻辑处理。
根据ipv4报头的定义,我们定义如下结构体:
// version and header Length struct Info { let byte: UInt8 var version: UInt8 { byte >> 4 } var headerLength: UInt8 { byte & 0b00001111 } } extension Info: CustomStringConvertible { var description: String { "[version: \(String(version, radix: 2)), headlerLength: \(String(headerLength, radix: 2))]" } } struct FFO { let byte: UInt16 var flags: UInt8 { UInt8(byte >> 13) } var framentedOffset: UInt16 { byte & 0b0001111111111111 } } extension FFO: CustomStringConvertible { var description: String { "[flags: \(String(flags, radix: 2)), framentedOffset: \(String(framentedOffset, radix: 2))]" } } // ipv4报头 struct IPV4Header { let info: Info // version and header Length let serviceType: UInt8 // 服务类型 let totalLength: UInt16 // IP 包总长度 let identifier: UInt16 // 标识符 let flagAndFrament: FFO // Flags and Framented offset let timeToLive: UInt8 // 生存时间 TTL let `protocol`: UInt8 // 协议 let checksum: UInt16 // 校验和 let sourceAddress: UInt32 let destinationAddress: UInt32 } extension IPV4Header: CustomStringConvertible { var saddr: String { let v4 = sourceAddress >> (32-8) let v3 = (sourceAddress & 0b00000000111111110000000000000000) >> 16 let v2 = (sourceAddress & 0b00000000000000001111111100000000) >> 8 let v1 = (sourceAddress << (32-8)) >> (32-8) return "\(v1).\(v2).\(v3).\(v4)" } var daddr: String { let v4 = destinationAddress >> (32-8) let v3 = (destinationAddress & 0b00000000111111110000000000000000) >> 16 let v2 = (destinationAddress & 0b00000000000000001111111100000000) >> 8 let v1 = (destinationAddress << (32-8)) >> (32-8) return "\(v1).\(v2).\(v3).\(v4)" } var description: String { """ [ info: \(info), serviceType: \(String(serviceType, radix: 2)), totalLength: \(totalLength), identifier: \(identifier), flagAndFrament: \(flagAndFrament), timeToLive: \(timeToLive), protocol: \(`protocol`), checksum: \(checksum), sourceAddress: \(saddr), destinationAddress: \(daddr) ] """ } }
接下来初始化ipv4Header结构体,并转成data二进制。以及将data转成 ipv4Header结构体
let v: UInt8 = 0b0100 let l: UInt8 = 0b1111 let info = Info(byte: v << 4 | l) let ffo = FFO(byte: 0b0101101110001110) var saddr = "192.168.0.1".components(separatedBy: ".").map({UInt8($0)!}) var daddr = "200.100.20.10".components(separatedBy: ".").map({UInt8($0)!}) let sad = saddr.withUnsafeMutableBytes({ (p) in p.load(as: UInt32.self) }) let dad = daddr.withUnsafeMutableBytes({ (p) in p.load(as: UInt32.self) }) var h = IPV4Header(info: info, serviceType: 0b00011110, totalLength: 10023, identifier: 10010, flagAndFrament: ffo, timeToLive: 10, protocol: 6, checksum: 12221, sourceAddress: sad, destinationAddress: dad) // struct 转 data var data = withUnsafeBytes(of: &h, { (bf) in let daa = bf.map({$0}) return Data(bytes: daa, count: daa.count) }) // data 转 struct var header = data.withUnsafeMutableBytes({ p in p.load(as: IPV4Header.self) }) print("===header: \(header)")
Unmanaged(非托管对象):如果直接使用指针,那么就需要我们手动管理内存,这个并不好办,所以苹果引入了Unmanaged来管理引用计数,Unmanaged 能够将由 C API 传递过来的指针进行托管,我们可以通过Unmanaged标定它是否接受引用计数的分配,以便实现类似自动释放的效果;同时,如果不是使用引用计数,也可以使用Unmanaged 提供的release函数来手动释放,这比在指针中进行这些操作要简单很多。
我们知道在OC中使用指针时,也涉及到内存管理,它是通过__bridge(只做类型转换,不拥有对象)、__bridge_transfer(转换类型,并释放原本对象的拥有权,将拥有权交给转换后的对象)、__bridge_retained(转换类型,并拥有其所有权)
那么在swift中就是通过Unmanaged来实现的,但是它只能转化AnyObject的class类型
public struct Unmanaged<Instance> where Instance : AnyObject
Unmanaged有三个static类型的初始化方法:
// 通过class的原始指针,初始化(不会增加该class的引用计数,引用计数不变) static func fromOpaque(_ value: UnsafeRawPointer) -> Unmanaged<Instance> // 通过一个实例对象,初始化(会增加该class实例的引用计数) static func passRetained(_ value: Instance) -> Unmanaged<Instance> // 通过一个实例对象,初始化(不会增加该class实例的引用计数) static func passUnretained(_ value: Instance) -> Unmanaged<Instance>
然后通过Unmanaged实例可以将class实例转成UnsafeMuableRawPointer,并且通过以下方法拿到这个实例对象
// 返回class实例对象,并不会让Unmanaged对其之前的引用-1 func takeUnretainedValue() -> Instance // 返回class实例对象,并让Unmanaged对其之前的引用-1 func takeRetainedValue() -> Instance
当然它还提供了以下方法,用于维护class实例对象的引用计数
// 引用计数+1 func retain() -> Unmanaged<Instance> // 引用计数-1 func release() // 自动引用计数 func autorelease() -> Unmanaged<Instance>
具体我们看一个例子,首先我们声明一个People类,注意:它是一个class类型的
class People { let name: String let age: Int8 init(name: String, age: Int8) { self.name = name self.age = age } deinit { print("People deinit") } }
然后声明一个临时持有People指针的类
class Monitor { static let share = Monitor() var ctx: UnsafeRawPointer? private var callback: ((UnsafeRawPointer?) -> Void)? func startMonitor(_ callback: @escaping (UnsafeRawPointer?)->Void) { self.callback = callback run() } private func run(){ DispatchQueue.main.asyncAfter(deadline: .now()+3) { if let call = self.callback{ call(self.ctx) } } } }
然后具体使用如下:
let people = People(name: "drbox", age: 24) // 因为people是局部变量,因此它在超出当前方法作用域后就会被释放,所以我们在将其指针传给Monitor对象的属性时,需要调用passRetained,来增加其引用计数,这样people就不会在方法执行完后而释放了 Monitor.share.ctx = UnsafeRawPointer(Unmanaged.passRetained(people).toOpaque()) Monitor.share.startMonitor { (ptr: UnsafeRawPointer?) -> Void in if let p = ptr{ // 3秒后会回调这个闭包函数,并将上面的people指针传回,ptr即为上面people的指针 let pp = Unmanaged<People>.fromOpaque(p).takeRetainedValue()// 由于上面对people增加了引用计数,因此这里在取people的值时,需要调用takeRetainedValue来对引用计数-1,这样一来people就会释放了 print("name: \(pp.name), age: \(pp.age)") } }
输出日志如下:
name: drbox, age: 24 People deinit
另一种实现方式如下:
let people = People(name: "drbox", age: 24) let um = Unmanaged.passUnretained(people) // 这里不会对people增加引用计数 // 因此需要手动调用retain增加引用计数 _ = um.retain() Monitor.share.ctx = UnsafeRawPointer(um.toOpaque()) Monitor.share.startMonitor { (ptr: UnsafeRawPointer?) -> Void in if let p = ptr{ // 3秒后会回调这个闭包函数,并将上面的people指针传回,ptr即为上面people的指针 let pp = Unmanaged<People>.fromOpaque(p).takeUnretainedValue()// takeUnretainedValue取出people实例,但不会对引用计数-1 print("name: \(pp.name), age: \(pp.age)") // 因此在我们不在用这个people实例后,需要调用其release对引用计数-1 Unmanaged<People>.fromOpaque(p).release() } }
输出日志:
name: drbox, age: 24 People deinit