Swift指针

308 阅读9分钟

1、指针

1.1、指针的不安全性

1.1.1、野指针
  • 我们创建对象需要在上分配内存空间.但内存空间的生命周期是有限的,也就是说,如果我们使⽤指针指向这块空间,后续内存空间的⽣命周期结束(引⽤计数为0),那么我们当前的指针就变成了未定义的⾏为
1.1.2、指针越界
  • 内存空间是有边界的,如果一个大小为10的数组我们通过指针访问index = 11的位置,指针就越界了
1.1.3、类型不一致
  • 比如存储的内容为 Int 类型,如果指针类型为 Int8 类型,那么会造成精度缺失

1.2、指针类型

数据类型计算工具:MemoryLayout<T>
  • 计算数据类型的工具 image.png
    • size:计算数据类型的实际大小
    • stride:计算数据的步长(连续的该类型存储,从第一个实例到下一个实例的内存长度)
    • alignment:字节对齐方式 image.png
常用指针类型
  • 常用的指针类型,带<T>表示 指定数据类型指针,带Raw表示 原生指针,带Buffer表示 连续内存地址 image.png
1.2.1、raw pointer
  • 未指定数据类型的指针(原⽣指针)

  • 原生指针存储与读取

    • UnsafeMutableRawPointer 使用 StoreBytesload 进行数据的存储与读取 image.png
      • .allocate(byteCount: Int X , alignment: Int Y):创建一个容量X字节、以Y字节对齐方式的内存空间
      • .advanced(by: Int M):向后偏移M字节(不加这个会导致值都存在在同一个地方)
      • .storeBytes(of: N , as: T.type):将T类型的值N存到指针中
      • .load(fromByteOffset: Int K as: T.type):偏移到第K个字节读取T类型的值
      • deallocate:回收内存空间
1.2.2、typed pointer
  • 指定数据类型指针(泛型指针)

  • 通过 withUnsafePointer 等来访问当前变量地址,数据存取不再通过StoreBytesload 进行,而是操作当前泛型指针内置的变量pointee

  • UnsafePointer 的⽅式有两种:

    • ⼀种⽅式就是通过已有变量获取: image.png
    • 还有⼀种⽅式就是直接分配内存: image.png 也可以通过advanced分配内存 image.png

2、内存绑定

  • Swift中有3中绑定/重新绑定指针的API:

2.1、assumingMemoryBound(to:):

  • 作用 : 绕过编译器检查并省去繁琐的类型转换 image.png

  • 有时我们处理代码过程中, 只有原始指针(没有保留指针类型),但此刻我们 明确知道指针的类型,那么我们就可以使⽤这个API来告诉编译器预期的类型(注意:只是绕过编译器类型检查,并没有发⽣实际类型的转换)

2.2、bindMemory(to: capacity:)

  • 作用 : 更改内存绑定的类型

  • 如果当前内存还没有类型绑定,则将⾸次绑定为该类型;否则重新绑定该类型,并且 内存中所有的值都会变成该类型

2.3、withMemoryRebound(to: capacity: body:):

  • 作用 : 临时更改内存绑定类型 image.png
  • 当我们在给外部函数传递参数时,不免会有⼀些数据类型上的差距。如果我们进⾏类型转换,必然要来回复制数据;这个时候我们就可以使⽤ withMemoryRebound(to: capacity: body:) ,更改的类型只在作用域内生效

3、内存管理(探索引用计数原理)

  • Swift 中使⽤⾃动引⽤计数(ARC)机制来追踪和管理内存;而内存管理最重要的就是 引用计数refCounts

  • 查看 refCounts 需要用到 实例变量的内存指针 ,因为 refCounts 存在于其头16字节的后8个字节中,而查看 实例变量的内存指针 ,需要用到下边的一个固定用法:

    let person = LZPerson()
    // 打印实例变量的内存指针 
    print(Unmanaged.passRetained(person as AnyObject).toOpaque())
    
  • 我们先用一段简单代码大概看一下是什么意思: image.png 在每次将实例p赋给其他对象持有后,我们发现在内存指针中,只有前16字节的的后8个字节产生了改变,印证了这个位置是 refCounts 的说法;下边将在源码的基础上分析强弱引用的区别

3.1、强引用

  • 既然找 refCounts 的源码,那么在源码 HeapObject.h 文件中搜索: image.png

    • 是一个InlineRefCounts的宏定义
  • 查看 InlineRefCounts 内容: image.png

    • 这里可以看到,通过RefCounts模板,根据传入的参数定义成:

      • InlineRefCounts : 传入InlineRefCountBits
      • SideTableRefCounts(弱引用使用) : 传入SideTableRefCountBits
    • 查看 RefCounts 模板结构: image.png

    • 查看RefCountBits结构: image.png

      • BitsType宏定义的 bits ,后续操作都有关于这个参数 image.png
      • 所以我们知道了,无论 Swift 还是 OC 里面的引用计数都是一个 64位的位域信息 ,在这64位位域信息中存储了当前类运行的生命周期相关的引用计数
  • refCounts 结构看完了,我们来看看初始化一个实例引用计数的变化,首先找创建方法: image.png

    • 进入方法查看 image.png image.png
    • 既然咱们又看到了 RefCountBits ,就来看看它到底是什么东西
  • RefCount.h文件找到了 image.png

    • StrongExtraRefCountShift : 33
    • UnownedRefCountShift : 1
    • PureSwiftDeallocShift : 1
  • 所以反过头我们看上边例子中得到的不明所以的 refCounts 数值 image.png

    • 例子中的引用计数分别为0、1、2,掏出计算器,把这3个 十进制 数分别 左移33位 后,再切换成十六进制,得到控制台中框出的数值
  • 既然数对上了,那么我们再来看一下咱们这个理所当然看出来的引用计数1、2、3是怎么来的,其实源码中是有一个函数swift_retainHeapObject.cpp文件中 image.png

    • increment():增量 image.png
      • inc 是1,下边 bits 就是每次加1 image.png

3.2、弱引用(weak)

  • Swift 提供了两种办法⽤来解决你在使⽤类的属性时所遇到的循环强引⽤问题: 弱引⽤(weak reference)⽆主引⽤(unowned reference)

  • 由于弱引⽤不会强保持对实例的引⽤,所以说实例被释放了弱引⽤仍旧引⽤着这个实例也是有可能的。因此,ARC 会在被引⽤的 实例被释放时⾃动地设置弱引⽤为nil 。由于弱引⽤需要允许它们的值为nil,所以它们⼀定得是可选类型

  • 通过汇编可知弱引用时会走一个swift_weakInit方法,那么就在源码中找这个方法 image.png

  • 创建⼀个弱引⽤的时候,系统会创建⼀个Side TableSide Table 是⼀种类名为 HeapObjectSideTableEntry 的结构,⾥⾯也有 RefCounts 成员,是内部是 SideTableRefCountBits ,其实就是原来的 uint64_t 加上⼀个存储弱引⽤数的 uint32_t 。 image.png

3.3、无主引用(unowned)

  • 和弱引⽤类似,⽆主引⽤不会牢牢保持住引⽤的实例。不是一个可选类型,不能设置为nil
  • 如果弱引用双方,生命周期没有任何关联,那么就使用 weak ,例如delegate
  • 如果一个对象销毁,另一个对象也跟着销毁,那么使用unowned

: 一般无脑weak,但是 unowned 的性能更好,因为 不用创建一个散列表 ,直接对refcounts的64位进行操作

4、闭包循环引用

  • 闭包内部对变量的修改将会改变外部原始变量的值

    • 如果我们在 class 的内部定义⼀个闭包,当前闭包访问属性的过程中,就会对我们当前的实例对象进⾏捕获,如果实例又调用了闭包,就产生循环引用了
  • 解决方法:使用 weak (或 unowned )修饰捕获列表

  • 使用类似OC中的strongSelf修饰weakSelf如何写:

    func test(){    
        let t = LZPerson()    
        t.testClosure = {[weak t] in     
            //方式一:
            if let strongSelf = t{
                print(strongSelf.age)        
            }        
    
            //方式二:
            withExtendedLifetime(t){          
                print( t!.age)        
            }    
        } 
    }
    
    • withExtendedLifetime:延长t的生命周期,周期范围是这个闭包表达式内

什么是捕获列表

  • 默认情况下,闭包表达式从其周围的范围捕获常量和变量,并强引⽤这些值。可以使⽤捕获列表来显式控制如何在闭包中捕获值。 

  • 在参数列表之前,捕获列表被写为⽤逗号括起来的表达式列表,并⽤⽅括号括起来。如果使⽤捕获列表,则即使省略参数名称,参数类型和返回类型,也必须使⽤in关键字。

5、指针读取Mach-O中的属性

5.1、归纳需要的数据

  • 首先,我们要知道,通过指针读取Mach-O中属性需要哪些数据:
    1. Section64(__TEXT,__swift5_types):TargetClassDescriptor
    2. ASLR(整个Mach-O文件偏移地址):image list方法得到的地址
    3. Mach-O中基地址:可以用以下两种思路得到:
      • 通过LC_SEGMENT_64(__PAGEZERO)VM Size 直接获取 image.png
      • 通过LC_SEGMENT_64(__LINKEDIT)VM AddressFile offset (偏移地址 - 偏移量)计算得到 image.png

5.2、计算数据所需方法

  • 在开始计算前,我们还需要知道几个方法:
    • _dyld_get_image_header(0):获取 ASLR image.png

    • getsegbyname( _ segname: UnsafePointer!) -> UnsafePointer<segment_command_64>! : 获取 Segment image.png

    • getsectdata( _ segname: UnsafePointer!, _ sectname: UnsafePointer!, _ size: UnsafeMutablePointer!) -> UnsafeMutablePointer! :获取 Section64 数据 image.png image.png

    • <T>(bitPattern: x ):为各种类型,因为很多类型可以调用这个方法

      • 作用是 将地址转为数值 进行计算
      • 按相同的二进制位,将 x 转换为 T 类型,但是需要注意源码中比如Int64、UInt64等等这些中x参数的类型要对上,也不是乱转的;下图中就是Int强转Int64给UInt64的bitPattern使用 image.png image.png

5.3、开始计算

  • 通过上边整理出需要的数据,与给出的获取 SegmentSection 方法,我们接下来就开始进行具体的计算:(具体数值计算要根据产生的Mach-O文件调整)
    /**
        __swift5_types section 的pFile
     */
    var size: UInt = 0  //为了拿到UInt的字节大小
    let ptr = getsectdata("__TEXT", "__swift5_types", &size)
    
    /**
        获取ASLR 0x0000000100000000
     */
    let mhHeaderPtr = _dyld_get_image_header(0)
    
    /**
        通过 __LINKEDIT 计算出Mach-O基地址
     */
    let setCommond64Ptr = getsegbyname("__LINKEDIT")
    var linkBaseAddress: UInt64 = 0
    if let vmaddr = setCommond64Ptr?.pointee.vmaddr, let fileOff = setCommond64Ptr?.pointee.fileoff{
        linkBaseAddress = vmaddr - fileOff
    }
    
    /**
        __swift5_types section 的pFile 数值
     */
    var offset: UInt64 = 0
    if let unwrappedPtr = ptr{
        let intRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: unwrappedPtr)))
        offset = intRepresentation - linkBaseAddress
    }
    
    /**
        DataLo的内存地址,__swift5_types 的Data LO前4字节
     */
    let mhHeaderPtr_IntRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: mhHeaderPtr)))
    var dataLoAddress = mhHeaderPtr_IntRepresentation + offset
    //print(UnsafePointer<UInt32>.init(bitPattern: Int(exactly: dataLoAddress) ?? 0)?.pointee)
    var dataLoAddressPtr = withUnsafePointer(to: &dataLoAddress){return $0}
    var dataLoContent = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: dataLoAddress) ?? 0)?.pointee
    
    /**
        计算出TargetClassDescriptor的地址
     */
    let typeDescOffset = UInt64(dataLoContent!) + offset - linkBaseAddress
    var typeDescAddress = typeDescOffset + mhHeaderPtr_IntRepresentation
    let classDescriptor = UnsafePointer<TargetClassDescriptor>.init(bitPattern: Int(exactly: typeDescAddress) ?? 0)?.pointee
    
    /**
        根据结构体计算 fieldDescriptor
     */
    if let name = classDescriptor?.name{
        let nameOffset = Int64(name) + Int64(typeDescOffset) + 8
        print(nameOffset)
        let nameAddress = nameOffset + Int64(mhHeaderPtr_IntRepresentation)
        print(nameAddress)
        if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(nameAddress)){
            print(String(cString: cChar))
        }
    }
    
    let filedDescriptorRelaticveAddress = typeDescOffset + 16 + mhHeaderPtr_IntRepresentation
    //let fieldDescriptor = UnsafePointer<FieldDescriptor>.init(bitPattern: Int(exactly: filedDescriptorAddress) ?? 0)?.pointe
    let fieldDescriptorOffset = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: filedDescriptorRelaticveAddress) ?? 0)?.pointee
    let fieldDescriptorAddress = filedDescriptorRelaticveAddress + UInt64(fieldDescriptorOffset!)
    let fieldDescriptor = UnsafePointer<FieldDescriptor>.init(bitPattern: Int(exactly: fieldDescriptorAddress) ?? 0)?.pointee
    
    /**
        计算FieldRecord
     */
    for i in 0..<fieldDescriptor!.numFields{
        let stride: UInt64 = UInt64(i * 12)
        let fieldRecordAddress = fieldDescriptorAddress + stride + 16
    //    print(fieldRecordRelactiveAddress)
    //    let fieldRecord = UnsafePointer<FieldRecord>.init(bitPattern: Int(exactly: fieldRecordAddress) ?? 0)?.pointee
    //    print(fieldRecord)
        let fieldNameRelactiveAddress = UInt64(2 * 4) + fieldRecordAddress - linkBaseAddress + mhHeaderPtr_IntRepresentation
        let offset = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: fieldNameRelactiveAddress) ?? 0)?.pointee
    //    print(offset)
        let fieldNameAddress = fieldNameRelactiveAddress + UInt64(offset!) - linkBaseAddress
        if let cChar = UnsafePointer<CChar>.init(bitPattern: Int(fieldNameAddress)){
            print(String(cString: cChar))
        }
    }