0
),那么我们当前的指针是不是就变成了未定义的⾏为了。我们创建的内存空间是有边界的,⽐如我们创建⼀个⼤⼩为10
的数组,这个时候我们通过指针访问到了index = 11
的位置,这个时候是不是就越界了,访问了⼀个未知的内存空间。指针类型与内存的值类型不⼀致,也是不安全的。
指针类型
Swift
中的指针分为两类, typed pointer
指定数据类型指针,raw pointer
未指定数据类型的指针(即原⽣指针)。基本上我们接触到的指针类型有⼀下⼏种:
Swift | Object-C | 说明 |
---|---|---|
unsafePointer | const T * | 指针可变,所指向的内容都不可变 |
unsafeMutablePointer | T * | 指针及其所指向的内存内容均可变 |
unsafeRawPointer | const void * | 指针指向的内存区域未定 |
unsafeMutableRawPointer | void * | 指针指向的内存区域未定 |
unsafeBufferPointer | 开辟连续的内存地址 | |
unsafeMutableBufferPointer | ||
unsafeRawBufferPointer | ||
unsafeMutableRawBufferPointer |
下面我们就体验一下这几种类型的指针:
原生指针的使用首先我们使用Raw Pointer
来存储4
个整形的数据,我们需要选取的是 UnsafeMutableRawPointer
。
下面我们输入一段代码
//存储4个Int 4 * 8 = 32byteCount 8字节alignment 对齐
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
for i in 0..<4 {
//存储 i
p.storeBytes(of: i, as: Int.self)
}
for i in 0..<4 {
let value = p.load(fromByteOffset: i * 8, as: Int.self)
print("index\(i) value\(value)")
}
打印结果为:
结果和我们想象的不一致,造成这个的原因,我们先从下面这段代码说起:
struct LLPerson {
var age: Int = 18 //8字节
var sex: Bool = true //1字节
}
print(MemoryLayout<LLPerson>.size) //打印大小
print(MemoryLayout<LLPerson>.stride) //打印步长
print(MemoryLayout<LLPerson>.alignment) //打印对齐
输出结果为:
size
是结构体的实际大小,stride
步长信息由于要8
字节对齐,所以步长信息是16
字节,就是在内存中连续存储LLPerson
所需要的内存。
在上面的例子中,p
是起始存储地址,在for
循环中p.storeBytes(of: i, as: Int.self)
就是无限循环向p
地址存储i
,所以打印的index0 value3
是i=3
时存储进去的,其他的值都是未初始化数据的值。接下来我们改造代码:
//存储4个Int 4 * 8 = 32byteCount 8字节alignment 对齐
let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
for i in 0..<4 {
//存储 i
// p.storeBytes(of: i, as: Int.self)
//advanced 移动指针地址 移动 i * Int类型步长
p.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i, as: Int.self)
}
for i in 0..<4 {
let value = p.load(fromByteOffset: i * MemoryLayout<Int>.stride, as: Int.self)
print("index\(i) value\(value)")
}
//使用了allocate 需要使用deallocate销毁
p.deallocate()
打印结果为:
index0 value0
index1 value1
index2 value2
index3 value3
这种情况就是正确的。
泛型指针的使用泛型指针相⽐较原⽣指针来说,其实就是指定当前指针已经绑定到了具体的类型,首先从一段代码看起:
var age = 18
age = withUnsafePointer(to: &age){ ptr in
return ptr.pointee + 21
// ptr.pointee + 21 也可以直接返回
}
print(age) //打印39
使用withUnsafePointer
,这种写法是可以修改age
的值的,但是当我们在闭包内部修改ptr.pointee
的值时,
编译器编译是不通过的,同时提醒我们他不是一个mutable
,即不是一个可变的,那我们可以使用对应的withUnsafeMutablePointer
,改造如下:
withUnsafeMutablePointer(to: &age) { ptr in
ptr.pointee += 21
}
print(age) //打印结果为39
在我们泛型指针的使用中,常用的方法有如下这些:
下面我们可以使用以下实例使用以下这些方法:
当我们使用UnsafeMutablePointer
创建内存时
struct LLPerson {
var age: Int
var height: Double
}
var tPtr = UnsafeMutablePointer<LLPerson>.allocate(capacity: 5)
//和C函数的数组一样,使用tPtr[0]来存值
tPtr[0] = LLPerson(age: 18, height: 20.0)
tPtr[1] = LLPerson(age: 19, height: 21.0)
defer { //实际开发中可以用defer包裹
tPtr.deinitialize(count: 5) //和deallocate承兑出现的 抹去数值
tPtr.deallocate() //回收内存
}
这种方式和C
语言中的数组一样,使用[]
来存取值。同时我们还可以使用advanced
这种方式来初始化值
tPtr.advanced(by: MemoryLayout<LLPerson>.stride).initialize(to: LLPerson(age: 18, height: 20.0))
指针读取macho中的属性名称
接下来,我们使用相关内容,通过指针读取macho
中的属性名称。
import MachO
class LLPerson {
var age: Int = 18
var name: String = "LL"
}
var size: UInt = 0
// 获取__swift5_types在内存中的地址
var ptr = getsectdata("__TEXT", "__swift5_types", &size)
//print(ptr)
//获取macho header的起始地址
var mhHeaderPtr = _dyld_get_image_header(0)
//print(mhHeaderPtr)
//segmentcommand的地址
var setCommond64Ptr = getsegbyname("__LINKEDIT")
//print(sectCommond64Ptr)
//计算基地址
var linkBaseAddress: UInt64 = 0
if let vmaddr = setCommond64Ptr?.pointee.vmaddr, let fileOff = setCommond64Ptr?.pointee.fileoff {
//虚拟内存地址 - 文件偏移量就是基地址
linkBaseAddress = vmaddr - fileOff
}
//print(linkBaseAddress)
//获取__swift5_types的偏移量
var offset: UInt64 = 0
if let unwrappedPtr = ptr {
let intRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: unwrappedPtr)))
//需要减去基地址
offset = intRepresentation - linkBaseAddress
}
//print(offset)
//获取DataLo的内存地址
//类型转换
let mhHeader_intRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: mhHeaderPtr)))
var dataLoAddress = mhHeader_intRepresentation + offset
//转换为指针类型
var dataLoAddressPtr = withUnsafePointer(to: &dataLoAddress){return $0}
//print(dataLoAddressPtr)
//获取dataLo内容
var dataLoContent = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: dataLoAddress) ?? 0)?.pointee
//print(dataLoContent)
//获取描述文件的偏移量 dataLo + 文件偏移量 - 基地址
let typeDescOffset = UInt64(dataLoContent!) + offset - linkBaseAddress
//获取描述文件的地址 偏移量 + macho的起始地址
let typeDescAddress = typeDescOffset + mhHeader_intRepresentation
//print(typeDescAddress)
//前面获取到的描述文件的结构
struct TargetClassDescriptor {
var flags: UInt32
var parent: UInt32
var name: Int32
var accessFunctionPointer: Int32
var fieldDescriptor: Int32
var superClassType: Int32
var metadataNegativeSizeInWords: UInt32
var metadataPositiveSizeInWords: UInt32
var numImmediateMembers: UInt32
var numFields: UInt32
var fieldOffsetVectorOffset: UInt32
var Offset: UInt32
var size: UInt32
}
//将typeDescAddress地址按照TargetClassDescriptor读取获取相关结构
let classDescriptor = UnsafePointer<TargetClassDescriptor>.init(bitPattern: Int(exactly: typeDescAddress) ?? 0)?.pointee
//print(classDescriptor)
if let name = classDescriptor?.name {
// 拿到结构中name的偏移量 name的值 + typeDescOffset(描述文件的偏移量) + flags(4字节) + parent(4字节)
let nameOffset = Int64(name) + Int64(typeDescOffset) + 8
// print(nameOffset)
// 拿到name的地址 偏移量 + 程序运行起始地址
let nameAddress = nameOffset + Int64(mhHeader_intRepresentation)
// print(nameAddress)
//以CChar形式读取
if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(nameAddress)) {
print(String(cString: cChar))
}
}
//拿到filedDescriptor的相对地址 typeDescOffset(描述文件的偏移量) + flags(4字节) + parent(4字节)+ name(4字节) + accessFunctionPointer(4字节) + 程序运行基地址
let fieldDescriptorRelativeAddress = typeDescOffset + 16 + mhHeader_intRepresentation
//print(fieldDescriptorRelativeAddress)
//filedDescriptor 内存结构
struct FieldDescriptor {
var mangledTypeName: Int32
var superclass: Int32
var Kind: UInt16
var fieldRecordSize: UInt16
var numFields: UInt32 //fieldRecords的个数
// var fieldRecords: [FieldRecord]
}
//fieldRecords 内存结构
struct FieldRecord {
var Flags: UInt32
var mangledTypeName: Int32
var fieldName: UInt32
}
//偏移地址 类型转换
let fieldDescriptorOffset = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: fieldDescriptorRelativeAddress) ?? 0)?.pointee
//print(fieldDescriptorOffset)
// 获取真实地址
let fieldDescriptorAddress = fieldDescriptorRelativeAddress + UInt64(fieldDescriptorOffset!)
//print(fieldDescriptorAddress)
// 转换成FieldDescriptor内存结构
let fieldDescriptor = UnsafePointer<FieldDescriptor>.init(bitPattern: Int(exactly: fieldDescriptorAddress) ?? 0)?.pointee
//print(fieldDescriptor)
// 读取属性
for i in 0..<fieldDescriptor!.numFields {
// 计算相应偏移 Flags(4字节) + mangledTypeName(4字节)+ fieldName(四字节)= 12
let stride: UInt64 = UInt64(i * 12)
//拿到每个FieldRecord的地址 fieldDescriptorAddress + mangledTypeName(4字节)+ superclass(4字节)+ Kind(2字节)+ fieldRecordSize(2字节)+ 每个FieldRecord的偏移
let fieldRecordAddress = fieldDescriptorAddress + 16 + stride
//获取fieldName的相对地址 Flags(4字节)+ mangledTypeName(4字节)+ 相对地址 - 基地址 + 程序运行起始地址
let fieldNameRelactiveAddress = UInt64(2 * 4) + fieldRecordAddress - linkBaseAddress + mhHeader_intRepresentation
//转换格式
let offset = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: fieldNameRelactiveAddress) ?? 0)?.pointee
//获取内存地址 fieldName的相对地址 + 偏移量 - 基地址
let fieldNameAddress = fieldNameRelactiveAddress + UInt64(offset!) - linkBaseAddress
// cChar类型读取
if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(fieldNameAddress)){
print(String(cString: cChar))
}
}
接下来,我们通过对比macho
文件对比获取的值
通过getsectdata("__TEXT", "__swift5_types", &size)
获取__swift5_types
的地址。
通过getsegbyname("__LINKEDIT")
获取segment_commond_64
的地址,从segment_command_64
的结构:
public struct segment_command_64 {
public var cmd: UInt32 /* for 64-bit architectures */ /* LC_SEGMENT_64 */
public var cmdsize: UInt32 /* includes sizeof section_64 structs */
public var segname: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar) /* segment name */
//VM Address
public var vmaddr: UInt64 /* memory address of this segment */
public var vmsize: UInt64 /* memory size of this segment */
//File Offset
public var fileoff: UInt64 /* file offset of this segment */
public var filesize: UInt64 /* amount to map from the file */
public var maxprot: vm_prot_t /* maximum VM protection */
public var initprot: vm_prot_t /* initial VM protection */
public var nsects: UInt32 /* number of sections in segment */
public var flags: UInt32 /* flags */
public init()
public init(cmd: UInt32, cmdsize: UInt32, segname: (CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar, CChar), vmaddr: UInt64, vmsize: UInt64, fileoff: UInt64, filesize: UInt64, maxprot: vm_prot_t, initprot: vm_prot_t, nsects: UInt32, flags: UInt32)
}
拿到vmaddr
(虚拟内存地址)和fileoff
(文件偏移量),相减就可以得到基地址。
通过减去基地址拿到__swift5_types
的偏移量。
通过程序地址加上偏移量拿到dataLo
的地址,从地址拿到相关内容。
dataLo
加上文件偏移再减去基地址,就拿到了描述文件的起始位置。
通过TargetClassDescriptor
结构读取数据。
从结构中获取name
进行读取,同理属性名称也通过该结构读取出来进行打印。
这就是我们从macho
中读取的数据。
Swift
提供了三种不同的 API
来绑定/重新绑定指针:
1、assumingMemoryBound(to:)
传入元组时,会报错。但当我们使用assumingMemoryBound
时,
func testPoint(_ p: UnsafePointer<Int>) {
print(p[0])
print(p[1])
}
// 定义一个元组
let tuple = (10, 20)
withUnsafePointer(to: tuple) { (tuplePtr: UnsafePointer<(Int, Int)>) in
testPoint(UnsafeRawPointer(tuplePtr).assumingMemoryBound(to: Int.self))
}
这种情况就可以了。
有些时候我们处理代码的过程中,只有原始指针(没有保留指针类型),但此刻对于处理代码的我们来说明确知道指针的类型,我们就可以使⽤ assumingMemoryBound(to:)
来告诉编译器预期的类型。 (注意:这⾥只是让编译器绕过类型检查,并没有发⽣实际类型的转换)。
2、bindMemory(to: capacity:)
func testPoint(_ p: UnsafePointer<Int>) {
print(p[0])
print(p[1])
}
// 定义一个元组
let tuple = (10, 20)
withUnsafePointer(to: tuple) { (tuplePtr: UnsafePointer<(Int, Int)>) in
testPoint(UnsafeRawPointer(tuplePtr).bindMemory(to: Int.self, capacity: 1))
}
⽤于更改内存绑定的类型,如果当前内存还没有类型绑定,则将⾸次绑定为该类型;否则重新绑定该类型,并且内存中所有的值都会变成该类型。
3、withMemoryRebound(to: capacity: body:)
func testPoint(_ p: UnsafePointer<Int8>) {
print(p)
}
let Uint8Ptr = UnsafePointer<Int8>.init(bitPattern: 10)
// 减少代码复杂程度
Uint8Ptr?.withMemoryRebound(to: Int8.self, capacity: 1) { (int8Ptr: UnsafePointer<Int8>) in
testPoint(int8Ptr)
}
当我们在给外部函数传递参数时,不免会有⼀些数据类型上的差距。如果我们进⾏类型转换,必然要来回复制数据;这个时候我们就可以使⽤ withMemoryRebound(to: capacity: body:)
来临时更改内存绑定类型。
Swift
中使⽤⾃动引⽤计数(ARC)
机制来追踪和管理内存。首先我们从refCounts
着手:
class LLPerson {
var age: Int = 8
var name: String = "LL"
}
var p = LLPerson()
//固定写法 打印p的内存指针
print(Unmanaged.passUnretained(p as AnyObject).toOpaque())
我们通过对源码的分析,可以得出refCount
一下结构:
我们可以通过实例观看一下变化:
通过位移运算左移33
位,高34
位,每次加2
。
同时,当我们使用强引用时会造成循环引用。下面我们来分析一下:
class LLTeacher {
var age: Int = 18
var name: String = "LL"
var subject: LLSubject?
}
class LLSubject{
var subjectName: String
var subjectTeacher: LLTeacher
init(_ subjectName: String, _ subjectTeacher: LLTeacher) {
self.subjectName = subjectName
self.subjectTeacher = subjectTeacher
}
}
var t = LLTeacher()
var subject = LLSubject.init("Swift进阶", t)
t.subject = subject
上⾯做这段代码是不是就产⽣了两个实例对象之前的强引⽤啊,Swift
提供了两种办法⽤来解决你在使⽤类的属性时所遇到的循环强引⽤问题:弱引⽤( weak reference )
和⽆主引⽤( unowned reference )
。
弱引⽤不会对其引⽤的实例保持强引⽤,因⽽不会阻⽌ ARC
释放被引⽤的实例。这个特性阻⽌了引⽤变为循环强引⽤。声明属性或者变量时,在前⾯加上 weak
关键字表明这是⼀个弱引⽤。
由于弱引⽤不会强保持对实例的引⽤,所以说实例被释放了弱引⽤仍旧引⽤着这个实例也是有可能的。 因此,ARC
会在被引⽤的实例被释放是⾃动地设置弱引⽤为 nil
。由于弱引⽤需要允许它们的值为 nil
, 它们⼀定得是可选类型。
class LLTeacher {
var age: Int = 18
var name: String = "LL"
}
weak var t = LLTeacher()
只是用weak
时,调用了swift_weakInit
,相当于定义了⼀个 WeakRefrence
对象,创建了一个散列表结构。
是⼀种类名为 HeapObjectSideTableEntry
的结构,⾥⾯也有 RefCounts
成员,是内部是 SideTableRefCountBits
,其实就是原来的 uint64_t
加上⼀个存储弱引⽤数的 uint32_t
。
和弱引⽤类似,⽆主引⽤不会牢牢保持住引⽤的实例。但是不像弱引⽤,总之,⽆主引⽤假定是永远有值的。当为nil
时会崩溃。
根据苹果的官⽅⽂档的建议。当我们知道两个对象的⽣命周期并不相关,那么我们必须使⽤ weak
。相反,⾮强引⽤对象拥有和强引⽤对象同样或者更⻓的⽣命周期的话,则应该使⽤ unowned
。
Weak VS unowned
如果两个对象的⽣命周期完全和对⽅没关系(其中⼀⽅什么时候赋值为nil
,对对⽅都没影响),请⽤ weak
如果你的代码能确保:其中⼀个对象销毁,另⼀个对象也要跟着销毁,这时候,可以(谨慎)⽤ unowned
打印Vtable
// 原来得到的结构
struct TargetMethodDescriptor {
var Flags: UInt32 //4字节
//存储的offset
var Impl: UInt32 //4字节
}
//classDescriptor!.size存储着方法的个数
for i in 0..<classDescriptor!.size{
//偏移量 = 描述文件偏移量 + 描述文件结构尺寸 + i * 方法size
let targetMethodOffset = Int(typeDescOffset) + MemoryLayout<TargetClassDescriptor>.size + Int(i) * MemoryLayout<TargetMethodDescriptor>.size
//运行起始地址 + 偏移量 = 方法地址
let targetMethodAddress = mhHeader_intRepresentation + UInt64(targetMethodOffset)
// 格式化数据结构
let targetMethod = UnsafePointer<TargetMethodDescriptor>.init(bitPattern: Int(exactly: targetMethodAddress) ?? 0)?.pointee
// 方法地址 = 地址 + Flags(4字节) + impl偏移量 - 基地址
let impAddress = targetMethodAddress + 4 + UInt64(targetMethod!.Impl) - linkBaseAddress
//打印出来就可以
let imp = IMP(bitPattern: UInt(impAddress))
LLTest.callImp(imp!)
}
// 相当于执行方法,方法中写入打印函数名称
+ (void)callImp:(IMP)imp {
imp();
}
一些参考swiftDump
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)