跳转至

指针&内存管理

指针不安全

  • 创建⼀个对象的时候,需要在堆分配内存空间。但是这个内存空间的声明周期是有限的,也就意味着如果使⽤指针指向这块内容空间,如果当前内存空间的⽣命周期啊到了(引⽤计数为0),那么当前的指针就变成了未定义的⾏为了(野指针)。
  • 我们创建的内存空间是有边界的,⽐如我们创建⼀个⼤⼩为10的数组,这个时候我们通过指针访问到了 index = 11 的位置,这个时候就越界了,访问了⼀个未知的内存空间。
  • 指针类型与内存的值类型不⼀致,也是不安全的。

指针类型

Swift中的指针分为两类:

typed pointer 指定数据类型指针。比如int bool

raw pointer 未指定数据类型的指针(原⽣指针)。不知道指针指向的数据类型是什么样的。

基本上我们接触到的指针类型有⼀下⼏种

SwiftObject-C说明
unsafePointer< T > 指定数据类型 const T *指针和指针指向的内容都不可变
unsafeMutablePointer< T > 指定数据类型 T *指针和指针所指向的内存内容均可变
unsafeRawPointerconst void *指针指向的内存区域未定
unsafeMutableRawPointervoid *同上
unsafeBufferPointer< T > (Buffer连续的内存空间)
unsafeMutableBufferPointer< T >
unsafeRawBufferPointer
unsafeMutableRawBufferPointer

Buffer开辟连续的内存空间

测量内存大小

struct HHTeacherST {
  var age: Int = 18
  var sex: Bool = true
}
print(MemoryLayout<HHTeacherST>.size)//9 实际大小
print(MemoryLayout<HHTeacherST>.stride)//16 步长信息
print(MemoryLayout<HHTeacherST>.alignment)//8 对齐

原生指针开辟内存,存取数据

使⽤ Raw Pointer 来存储 4 个整形的数据,这⾥我们需要选取的是UnsafeMutableRawPointer

1、首先开辟一块内存空间UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)

byteCount:当前总的字节大小。int是4字节 占32。

alignment:对齐的大小。8字节对齐。

2、调用store方法存储当前的整型数值

3、调用load方法加载当前内存当中,这里的fromByteOffset就是距离首地址的字节大小,每次移动i*8的字节

let p = UnsafeMutableRawPointer.allocate(byteCount: 32, alignment: 8)
//存 storeBytes as:存的类型
for i in 0..<4{
  //p是起始地址,基地址
  //移动p:advanced,存的时候需要移动指针(移动步长信息)
  p.advanced(by: i * MemoryLayout<Int>.stride).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)")
}
//销毁内存p
p.deallocate()

泛型指针

        var age = 18
    withUnsafePointer(to: &age) { ptr in
        print(ptr)//打印age的内存指针
    }
    //修改指针
    age = withUnsafePointer(to: &age) { ptr in
        ptr.pointee + 21//pointee指针指向的数据类型
    }
    //    withUnsafeMutablePointer(to: &age) { prt in
    //        ptr.pointee += 10
    //    }
    print(age)

分配内存,指针访问结构体

func pointVS() {
    struct LGStruct{
        var age: Int
        var height: Double
    }
    let tPtr = UnsafeMutablePointer<LGStruct>.allocate(capacity: 5)//5个LGStruct
    tPtr[0] = LGStruct(age: 18, height: 20.9)
    tPtr[1] = LGStruct(age: 19, height: 21.9)

    tPtr.deinitialize(count: 5)//把内存空间中数据抹0
    tPtr.deallocate()//回收内存
    //或者
    //    tPtr.advanced(by: <#T##Int#>).initialize(to: <#T##LGStruct#>)
}

指针读取machO中的属性名称

import MachO
func loadMachO() {
    class HHTeacherM{
        var age: Int = 18
        var name: String = "machh"
    }

    var size: UInt = 0
    //macho中__swift5_types section 的pFile
    let ptr = getsectdata("__TEXT", "__swift5_types", &size)
    //print(ptr)

    //获取当前程序运行地址 0x0000000100000000
    let mhHeaderPtr = _dyld_get_image_header(0)
    let setCommond64Ptr = getsegbyname("__LINKEDIT")

    //链接的基地址
    var linkBaseAddress: UInt64 = 0
    if let vmaddr = setCommond64Ptr?.pointee.vmaddr, let fileOff = setCommond64Ptr?.pointee.fileoff{
        linkBaseAddress = vmaddr - fileOff
    }

    var offset: UInt64 = 0
    if let unwrappedPtr = ptr{
        let intRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: unwrappedPtr)))
        offset = intRepresentation - linkBaseAddress
    }

    //DataLo的内存地址
    let mhHeaderPtr_IntRepresentation = UInt64(bitPattern: Int64(Int(bitPattern: mhHeaderPtr)))

    var dataLoAddress = mhHeaderPtr_IntRepresentation + offset
    //print(UnsafePointer<UInt32>.init(bitPattern: Int(exactly: dataLoAddress) ?? 0)?.pointee)

    let dataLoAddressPtr = withUnsafePointer(to: &dataLoAddress){return $0}
    print(dataLoAddressPtr)

    let dataLoContent = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: dataLoAddress) ?? 0)?.pointee

    let typeDescOffset = UInt64(dataLoContent!) + offset - linkBaseAddress

    let typeDescAddress = typeDescOffset + mhHeaderPtr_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
    }

    let classDescriptor = UnsafePointer<TargetClassDescriptor>.init(bitPattern: Int(exactly: typeDescAddress) ?? 0)?.pointee


    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
    //print(filedDescriptorAddress)

    struct FieldDescriptor  {
        var mangledTypeName: Int32
        var superclass: Int32
        var Kind: UInt16
        var fieldRecordSize: UInt16
        var numFields: UInt32
        //    var fieldRecords: [FieldRecord]
    }

    struct FieldRecord{
        var Flags: UInt32
        var mangledTypeName: Int32
        var fieldName: UInt32
    }

    let fieldDescriptorOffset = UnsafePointer<UInt32>.init(bitPattern: Int(exactly: filedDescriptorRelaticveAddress) ?? 0)?.pointee
    //print(fieldDescriptorOffset)
    let fieldDescriptorAddress = filedDescriptorRelaticveAddress + UInt64(fieldDescriptorOffset!)

    let fieldDescriptor = UnsafePointer<FieldDescriptor>.init(bitPattern: Int(exactly: fieldDescriptorAddress) ?? 0)?.pointee


    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))
        }
    }

}

引用计数

64位信息,strong+unowned

使⽤强引⽤就会造成⼀个问题:循环引⽤。我们来看⼀个经典的循环

引⽤案例:

class LGTeacher{
  var age: Int = 18
  var name: String = "Kody"
  var subject: LGSubject?
}
class LGSubject{
  var subjectName: String
  var subjectTeacher: LGTeacher
  init(_ subjectName: String, _ subjectTeacher: LGTeacher) {
    self.subjectName = subjectName
    self.subjectTeacher = subjectTeacher
  }
}
var t = LGTeacher()
var subject = LGSubject.init("Swift进阶", t)
t.subject = subject

//实例对象的内存指针
print(Unmanaged.passUnretained(t as AnyObject).toOpaque())

上⾯做这段代码就产⽣了两个实例对象之前的强引⽤, Swift 提供了两种办法⽤来解决在使⽤类的属性时所遇到的循环强引⽤问题:弱引⽤( weak reference )和⽆主引⽤( unowned reference )。

weak弱引⽤

弱引⽤不会对其引⽤的实例保持强引⽤,因⽽不会阻⽌ ARC 释放被引⽤的实例。这个特性阻⽌了引⽤变为循环强引⽤。声明属性或者变量时,在前⾯加上weak关键字表明这是⼀个弱引⽤。

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

  • 弱引用不会增加实例的引用计数,因此不会阻止ARC销毁被引用的实例。这种特性使得引用不会变成强引用环。声明属性或者变量的时候,关键字weak表明引用为弱引用。
  • 弱引用只能声明为变量类型,因为运行时它的值可能改变。弱引用绝对不能声明为常量。
  • 因为弱引用可以没有值,所以声明弱引用的时候必须是可选类型的。在Swift语言中,推荐用可选类型来作为可能没有值的引用的类型。

unowned无主引用

  • 和弱引用相似,无主引用也不强持有实例。但是和弱引用不同的是,无主引用默认始终有值。因此,无主引用只能定义为非可选类型(non-optional type)。在属性、变量前添加 unowned 关键字,可以声明一个无主引用。
  • 因为是非可选类型,因此当使用无主引用的时候,不需要展开,可以直接访问。不过非可选类型变量不能赋值为 nil,因此当实例被销毁的时候,ARC 无法将引用赋值为 nil。
  • 当实例被销毁后,试图访问该实例的无主引用会触发运行时错误。使用无主引用时请确保引用始终指向一个未销毁的实例。

根据苹果的官⽅⽂档的建议。当两个对象的⽣命周期并不相关,那么我们必须使⽤ weak。相反,⾮强引⽤对象拥有和强引⽤对象同样或者更⻓的⽣命周期的话,则应该使⽤ unowned。

weak VS unowned

  • 如果两个对象的⽣命周期完全和对⽅没关系(其中⼀⽅什么时候赋值为nil,对对⽅都没影响),请⽤ weak(例delegate)。
  • 如果你的代码能确保:其中⼀个对象销毁,另⼀个对象也要跟着销毁,这时候,可以(谨慎)⽤ unowned。

如果不确定的话,直接使用weak。unowned性能稍好,直接操作位域信息。weak会创建side表,对散列表操作。

weak修饰的是一个可选类型,unowned不是可选类型,假定有值。所以weak安全一点。

• 弱引用和无主引用允许引用环中的一个实例引用另外一个实例,但不是强引用。因此实例可以互相引用,但是不会产生强引用环。

• 对于生命周期中引用会变为 nil 的实例,使用弱引用;对于初始化时赋值之后引用再也不会赋值为 nil

的实例,使用无主引用。

闭包循环引用

将一个闭包赋值给类实例的某个属性,并且这个闭包使用了实例,这样也会产生强引用环。这个闭包可能访问了实例的某个属性,例如 self.someProperty,或者调用了实例的某个方法,例如self.someMethod。这两种情况都导致了闭包使用 self,从而产生了循环引用。

⾸先我们的闭包会⼀般默认捕获我们外部的变量

var age = 18

let closure = {
  age += 1
}

closure()
print(age)

从打印结果19可以看出来

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

那同样就会有⼀个问题,如果在 class 的内部定义⼀个闭包,当前闭包访问属性的过程中,就会对当前的实例对象进⾏捕获:

class HHTeacherC{
    var age = 18

    var closure:(()->())?

    //通过打印deinit来查看当前对象是否释放
    deinit {
        print("HHTeacher deinit")
    }
}
func closureFunc() {
    let t = HHTeacherC()
    t.closure = {
        t.age += 1
    }
}

控制台不输出deinit

解决上面的循环引⽤

闭包引用循环解决

强弱共舞

class HHTeacherC{
    var age = 18

    var closure:(()->())?

    //通过打印deinit来查看当前对象是否释放
    deinit {
        print("HHTeacher deinit")
    }
}
func closureFunc() {
    let t = HHTeacherC()

    //使用weak
    t.closure = { [weak t] in
        t!.age += 1

        // MARK: weak strong dance
        //网络请求时
        if let strongSelf = t{//对可选值t绑定 解绑给strongSelf
            print(strongSelf.age)
        }
        //或者 延长t的生命周期(在闭包表达式t.closure的范围)
        withExtendedLifetime(t){
            t!.age
        }
    }

    //使用unowned
    t.closure = { [unowned t] in
        t.age += 1
    }
}