Swift指针&内存管理

一、指针

  

1、指针类型

  Swift中的指针分为两类:指定数据类型的指针(typed pointer);未指定数据类型的指针,也叫原生指针(raw pointer),基本上有以下:

Swift OC 备注
unsafePointer<T> const T * 指针及所指向的内容都不可变
unsafeMutablePointer<T> T * 指针及所指向的内存内容均可变
unsafeRawPointer const void * 指针指向的内存区域未定
unsafeMutableRawPointer void * 指针指向的内存区域未定
unsafeBufferPointer<T> 指针指向指定类型的连续缓冲区不可变
unsafeMutableBufferPointer<T> 指针指向指定类型的连续缓冲区可变
unsafeRawBufferPointer 指针指向未定类型的连续缓冲区不可变
unsafeMutableRawBufferPointer 指针指向未定类型的连续缓冲区可变

2、原始指针的使用

我们先了解一下MemoryLayout <T>指定类型的内存布局这个枚举类型。

public enum MemoryLayout<T> {
   public static var size: Int { get }
   public static var stride: Int { get }
   public static var alignment: Int { get }
   public static func size(ofValue value: T) -> Int
   public static func stride(ofValue value: T) -> Int
   public static func alignment(ofValue value: T) -> Int
   public static func offset(of key: PartialKeyPath<T>) -> Int?
}

成员/方法 例子 说明
var size: Int MemoryLayout <Int>.size 大小
var stride: Int MemoryLayout <Int>.stride 存储从一个实例到下一个实例所需要的字节数量,也叫步长.
var alignment: Int MemoryLayout <Int>. alignment 指定类型的默认内存对齐
func size(ofValue value: T) -> Int MemoryLayout.size(ofValue: "10.0") 根据值计算内存大小
func stride(ofValue value: T) -> Int MemoryLayout.stride(ofValue: PSYModel()) 根据值计算步长
func alignment(ofValue value: T) -> Int MemoryLayout. alignment(ofValue: PSYModel()) 根据值计算内存对齐字节
func offset(of key: PartialKeyPath<T>) -> Int? --
struct PSYModel {
    var age: Int = 18
    var name: String = "psy"
    var femalSex: Bool = false
}
print(MemoryLayout<String>.size) // String类型大小
print(MemoryLayout<PSYModel>.size) // 当前结构体类型的具体大小
print(MemoryLayout<PSYModel>.stride) // 当前结构体实例的步长信息
print(MemoryLayout<PSYModel>.alignment) // 结构体内存对齐

输出:
16
25
32
8

eg: 使用Raw Pointer来存储4个整形的数据。

// 首先申请4个整形数据大小的内存
let p = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout<Int>.size * 4, alignment: MemoryLayout<Int>. alignment)

// 遍历循环并根据偏移存入数据
for i in 0..<4{
    p.storeBytes(of: i, toByteOffset: (MemoryLayout<Int>.stride * i), as: Int.self)
    //或者// p.advanced(by: i * MemoryLayout<Int>.stride).storeBytes(of: i, as: Int.self)
    //或者// (p + i * 8).storeBytes(of: i, as: Int.self)
}

print(p)

// 循环根据偏移p.load读取内存值
for i in 0..<4{
    let value = p.load(fromByteOffset: i*8, as: Int.self)
    print("index:\(i), value:\(value)")
}
// 释放内存
p.deallocate() 

3、泛型指针的使用

  泛型指针相对于原生指针,其是指定了当前绑定了具体的类型。在访问泛型指针时并不是像原生指针那样通过storeload访问内存,而是通过泛型指针内置属性pointee访问。

获取到UnsafePointer的方式有两种:

第一种: 通过已知变量获取
使用withUnsafePointer访问变量内存地址

var age = 3
withUnsafePointer(to: &age){ptr in
   print("地址:(ptr) 值:(ptr.pointee)")
}
  
age = withUnsafePointer(to: &age){ptr in
   return ptr.pointee + 21
}
print("age = (age)") // age = 24

要想在withUnsafePointer的尾随闭包中修改age的值,就要使用withUnsafeMutablePointer,如下:

withUnsafeMutablePointer(to: &age){ ptr in
   ptr.pointee += 21
}
  
print(age) // 24

第二种: 直接分配内存

var age = 10
// 分配一块Int类型的内存空间,此时该内存空间并没有被初始化
let tPtr = UnsafeMutablePointer<Int>.allocate(capacity: 1)
// 分配的内存空间初始化
tPtr.initialize(to: age)
// 通过pointee的属性访问内存的值
print(tPtr.pointee)
tPtr.deallocate() // 释放

struct PSYModel{
  var age: Int
  let name: String
  var height: Double
}
// 为结构体类型的指定数量的实例分配未初始化的内存。
var strPtr = UnsafeMutablePointer<PSYModel>.allocate(capacity: 5)
// 内存初始化存储
strPtr[0] = PSYModel(age: 18, name: "PSY", height: 185)
strPtr[1] = PSYModel(age: 17, name: "俏~", height: 170)
strPtr[2] = PSYModel(age: 11, name: "LH", height: 163)
// 将内存空间清空(一般与deallocate成对出现)
strPtr.deinitialize(count: 3)
// 释放
strPtr.deallocate()

内存初始化存储还可以将strPtr[0] = PSYModel(age: 18, name: "PSY", height: 185)
改成:
(strPtr + 0).initialize(to: PSYModel(age: 18, name: "PSY", height: 185.0))
或者:
strPtr.advanced(by: MemoryLayout<PSYModel>.stride * 0).initialize(to: PSYModel(age: 18, name: "PSY", height: 185))
以上这三中初始化方式是等价的。
deinitialize(count: 3)是要根据上面初始化的数量,进行清空内存,初始化了 3个count就传 3

图示

4、内存指针的使用

将一个对象转换成结构体指针,期间因为需要对指针操作,所以需要管理内存,既繁琐有不安全,所以使用Unmanaged(非托管),通过passUnretained(_ value: Instance). toOpaque()转成指针,并不对引用计数执行+1操作,所以不用管理其内存。相对应的还有一个passRetained(_ value: Instance). toOpaque(),其会对实例对象的引用计数执行+1操作,所以使用需注意。

struct HeapObject{ // 堆内存中的结构体
  var metadata: UnsafeRawPointer // metadata原生指针
  var refcount1: Int32 // Int64, 可拆成两个Int32位
  var refcount: Int32
}
  
struct MetaData{ // metadata的结构体
   var metadata: UnsafeRawPointer
   var supperclass: UnsafeRawPointer
   var cacheData1: UnsafeRawPointer
   var cacheData2: UnsafeRawPointer
   var data: UnsafeRawPointer
   var flags: UInt32
   var instanceAddressOffset: UInt32
   var instanceSize: UInt32
   var instanceAlignMask: UInt16
   var reserved: UInt16
   var classSize: UInt32
   var classAddressOffset: UInt32
   var description: UnsafeRawPointer
}
  
class PSYModel{
  var age: Int = 18
   let name: String = "PSY"
}
  
var psy = PSYModel()
  
// 拿到实例的指针,但是对实例的引用计数没有影响(Unmanaged非托管的,将实例对象的转成指针,但是对引用计数不执行+1操作)
let psyRawPtr = Unmanaged.passUnretained(psy as AnyObject).toOpaque()
// 绑定成具体的结构体类型
let objPtr = psyRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
  
let metadata = objPtr.pointee.metadata.bindMemory(to: MetaData.self, capacity: 1)
print("HeapObject:*********")
print(objPtr.pointee)
print("\n"); print("\n")
print("MetaData:*********")
print(metadata.pointee)

输出及分析流程图

5、内存绑定

Swift提供了三种不同的API来绑定/重绑定指针

  • assumingMemoryBound(to:)
      
    使用场景:在处理代码时,只有原是指针(UnsafeRawPointer),没有保留指针类型,但是我们明确知道指针的类型,此时用assumingMemoryBound(to:)绑定成指定类型。此时编译器不进行此类型匹配检查,所以不需要类型转换。
      
    eg:报错案例
    报错案例

eg:正确用法

func testTuple(_ ptr: UnsafePointer<Int>){
    print(ptr[0])
    print(ptr[1])
}

let tuple = (12, 18)

withUnsafePointer(to: tuple){ (tuplePtr: UnsafePointer<(Int, Int)>) in
    testTuple(UnsafeRawPointer(tuplePtr).assumingMemoryBound(to: Int.self))
}

UnsafeRawPointer(tuplePtr) 将tuplePtr转成原生指针,然后调用assumingMemoryBound(to: T.Type)绑定成指定类型

  • bindMemory(to: capacity:)

  将内存绑定到指定的类型,并返回一个指向绑定内存的类型化指针。使用bindMemory(to:capacity:)方法将这个指针引用的内存绑定到类型' T '上。内存必须未初始化或初始化为布局与' T '兼容的类型。如果内存未初始化,则绑定到' T '后仍未初始化。

eg:

let count = 4
let bytesPointer = UnsafeMutableRawPointer.allocate(byteCount: 100,alignment: MemoryLayout<Int8>.alignment)
let int8Pointer = bytesPointer.bindMemory(to: Int8.self, capacity: count)

  • withMemoryRebound(to: capacity: body:)

  执行给定的闭包,同时将指定数量的实例临时绑定到指定类型。
当您有一个内存指针绑定到一种类型,而您需要作为另一种类型的实例访问该内存时,请使用此方法。以“T”类型访问内存需要将内存绑定到该类型。一个内存位置一次只能绑定到一种类型,因此访问与不相关的类型相同的内存而不首先重新绑定该内存是没有定义的。

从这个指针开始的内存区域,包括指针的' Pointee '类型的' count '实例必须被初始化。

// 一个指向指定类型的已初始化指针
let uint64Pointer: UnsafePointer<UInt64> = 初始值
// 临时重绑定到Int64类型作为临时使用
let isNegative = uint64Pointer.withMemoryRebound(to: Int64.self, capacity: 1) { ptr in
       return ptr.pointee < 0
}

二、内存管理

Swift中使用自动引用计数机制(ARC)来管理内存。那就离不开引用计数refCounts,用代码创建一个实例,看一下其内存引用计数是多少。

案例结果

查看源码看看HeapObject结构体中引用计数Refcount是如何定义的:

按住command + 单击可查看其定义和引用的地方,可发现其是一个模板类typedef RefCounts<InlineRefCountBits> InlineRefCounts;其是RefCounts类,接收的是InlineRefCountBits

InlineRefCounts定义
RefCounts类
RefCountsBitsT类

RefCounts类接收的InlineRefCountBits,其定义是typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits,是RefCountBitsT基于这个类接收RefCountIsInline,而找到RefCountIsInline其实是一个枚举,如果RefCountIsInline = true,则引用计数采用一定的编码算法存储在对象中;如果RefCountNotInline = false,则引用计数存储在散列表中。

image.png

再从一个对象初始化分配内存的时候引用计数到底做了啥?


_swift_allocObject_
image.png
RefCounts初始化化

可找到初始化一个对象的引用数是1:constexpr RefCounts(Initialized_t) : refCounts(RefCountBits(0, 1)) {},其接收的是RefCountBits这个类型,正是该方法RefCounts类接收的类型:


所以此时找到RefCountBitsT类的初始化方法:
RefCountBitsT初始化方法

其中的StrongExtraRefCountShiftPureSwiftDeallocShiftUnownedRefCountShift的值如下分析:

static const size_t PureSwiftDeallocShift = 0;
  static const size_t PureSwiftDeallocBitCount = 1;

  static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
  static const size_t UnownedRefCountBitCount = 31;

static const size_t IsDeinitingShift = shiftAfterField(UnownedRefCount);
  static const size_t IsDeinitingBitCount = 1;

# define maskForField(name) (((uint64_t(1)<<name##BitCount)-1) << name##Shift)
# define shiftAfterField(name) (name##Shift + name##BitCount)

static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
  static const size_t StrongExtraRefCountBitCount = 30;

StrongExtraRefCountShift = shiftAfterField(IsDeiniting) 
                         = IsDeinitingShift + IsDeinitingBitCount 
                         = shiftAfterField(UnownedRefCount) + IsDeinitingBitCount
                         = UnownedRefCountShift + UnownedRefCountBitCount + IsDeinitingBitCount
                         = PureSwiftDeallocShift + PureSwiftDeallocBitCount + UnownedRefCountBitCount + IsDeinitingBitCount
                         = 0 + 1 + 31 + 1 
                         = 33

PureSwiftDeallocShift    = 0
UnownedRefCountShift     = 1


所以初始化方法变成了:
constexpr
  RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
    : bits((BitsType(strongExtraCount) << 33 |  (BitsType(1)  << 0) | (BitsType(unownedCount) << 1)
  { }

再根据传入的初始值,strongExtraCount = 0,unownedCount = 1
constexpr
  RefCountBitsT(0, 1) : bits((0 << 33 |  (1  << 0) | (1 << 1){ }

0 << 33 = 0
1  << 0 = 1
1 << 1 = 2

最终结果是:0 | 1 | 2 = 3

跟代码运行的结果 2 不匹配,个人认为应该是系统版本和Xcode版本不匹配导致的(笔者Mac OS 10.14.6,Xcode 11.3,而笔者Swift是最新的5.6),找了以前的源码,初始化方法中并没有(BitsType(1) << 0),也就少了1,最终的值也就是 2,真好和我当前的Xcode测试结果一致。

1、强引用

强引用到底是如何对Refcount操作呢,笔者通过汇编发现其实际调用的是swift_retain方法:

汇编调用swift_retain

然后找到源码,实际是调用对象的属性refCountsincrement方法:object->refCounts.increment(1);

源码swift_retain
// 引用计数加 1 的方法
SWIFT_ALWAYS_INLINE
  void increment(uint32_t inc = 1) {
    auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
    
    // constant propagation will remove this in swift_retain, it should only
    // be present in swift_retain_n
    if (inc != 1 && oldbits.isImmortal(true)) {
      return;
    }
    
    RefCountBits newbits;
    do {
      newbits = oldbits;
       // inc = 1, 调用incrementStrongExtraRefCount(1)函数
      bool fast = newbits.incrementStrongExtraRefCount(inc);
      if (SWIFT_UNLIKELY(!fast)) {
        if (oldbits.isImmortal(false))
          return;
        return incrementSlow(oldbits, inc);
      }
    } while (!refCounts.compare_exchange_weak(oldbits, newbits,
                                              std::memory_order_relaxed));
  }
                                    ||
                                    ||
                                   \||/
                                     V

// incrementStrongExtraRefCount(1)函数,返回True,就表示只有inlineRefcount,返回false:表示有溢出,表明有散列表
SWIFT_NODISCARD SWIFT_ALWAYS_INLINE bool
  incrementStrongExtraRefCount(uint32_t inc) {
    // bits += 1<< 33
    bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
    return (SignedBitsType(bits) >= 0);
  }
                                    ||
                                    ||
                                   \||/
                                     V
1<<33

所以上面示例打印内存得到的引用计数的值就可以得到验证了

image.png

  然而强引用会带来的风险是,如果A强引用B,B强引用A(你中有我,我中有你),就造成了循环引用,导致的直接后果是对象得不到释放,导致内存泄漏,最终可能导致内存溢出。

eg:典型的 “我中有你,你中有我” 循环引用案例

class PSYModel{
    var age: Int = 19
    let name: String = "PSY"
    var girlFriend: QLRModel?
}

class QLRModel{
    var age: Int = 18
    let name: String = "LQR"
    var boyFriend: PSYModel?
    init(_ age: Int, _ husband: PSYModel) {
        self.age = age
        self.boyFriend = husband
    }
}
let psy = PSYModel()

let lqr = QLRModel.init(18, psy)

print("end")

Swift中解决循环引用的两种方式:使用弱引用(weak reference)和无主引用(unowned reference)。

2、弱引用

所以在声明属性或实例变量时,在前面加上weak关键字表明其实一个弱引用。弱引用并不会强持有实例对象,在ARC中,实例对象被释放的时候,弱引用会被置空为nil(可选类型),这样就不会导致内存泄漏。

弱引用
class PSYModel{
    var age: Int = 19
    let name: String = "PSY"
}
weak var psy = PSYModel()
print("end")
调用swift_weakInit

全局搜索源码:


swift_weakInit方法
WeakReference类

swift_weakInit里面调用了ref->nativeInit(value),是属于WeakReference方法,

// nativeInit方法
 void nativeInit(HeapObject *object) {
    auto side = object ? object->refCounts.formWeakReference() : nullptr;
    nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
  }
                                    ||
                                    ||       // refCounts.formWeakReference
                                   \||/
                                     V
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}
                                    ||
                                    ||      // setNativeOrNull
                                   \||/
                                     V
 // 如果有必要,创建并返回对象的散列表.
// 如果对象正在销毁,返回空.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
  auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
  
// 做一些必要的预处理
  if (oldbits.hasSideTable()) { // 如果已存在散列表直接返回.
    return oldbits.getSideTable();
  } 
  else if (failIfDeiniting && oldbits.getIsDeiniting()) { // 如果对象正在销毁,返回空.
    return nullptr;
  }
  
  // FIXME: 创建散列表
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  // 初始化
  auto newbits = InlineRefCountBits(side);
  
  do {
    if (oldbits.hasSideTable()) {
      // 如果旧的有散列表,返回它,并删除新创建的。
      // Read before delete to streamline barriers.
      auto result = oldbits.getSideTable();
      delete side;
      return result;
    }
    else if (failIfDeiniting && oldbits.getIsDeiniting()) {
      // Already past the start of deinit. Do nothing.
      return nullptr;
    }
    
  // 
    side->initRefCounts(oldbits);
    
  } while (! refCounts.compare_exchange_weak(oldbits, newbits,
                                             std::memory_order_release,
                                             std::memory_order_relaxed));
  return side;
}
image.png

同样像强引用一样找到RefCountBitsT以一个HeapObjectSideTableEntry初始化的方法:

SWIFT_ALWAYS_INLINE
  RefCountBitsT(HeapObjectSideTableEntry* side)
    : bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
           | (BitsType(1) << Offsets::UseSlowRCShift)
           | (BitsType(1) << Offsets::SideTableMarkShift))
  {
    assert(refcountIsInline);
  }


static const size_t PureSwiftDeallocShift = 0;
  static const size_t PureSwiftDeallocBitCount = 1;
  static const uint64_t PureSwiftDeallocMask = maskForField(PureSwiftDealloc);

  static const size_t UnownedRefCountShift = shiftAfterField(PureSwiftDealloc);
  static const size_t UnownedRefCountBitCount = 31;
  static const uint64_t UnownedRefCountMask = maskForField(UnownedRefCount);

  static const size_t IsImmortalShift = 0; // overlaps PureSwiftDealloc and UnownedRefCount
  static const size_t IsImmortalBitCount = 32;
  static const uint64_t IsImmortalMask = maskForField(IsImmortal);

  static const size_t IsDeinitingShift = shiftAfterField(UnownedRefCount);
  static const size_t IsDeinitingBitCount = 1;
  static const uint64_t IsDeinitingMask = maskForField(IsDeiniting);

  static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
  static const size_t StrongExtraRefCountBitCount = 30;
  static const uint64_t StrongExtraRefCountMask = maskForField(StrongExtraRefCount);
  
  static const size_t UseSlowRCShift = shiftAfterField(StrongExtraRefCount);
  static const size_t UseSlowRCBitCount = 1;
  static const uint64_t UseSlowRCMask = maskForField(UseSlowRC);

  static const size_t SideTableShift = 0;
  static const size_t SideTableBitCount = 62;
  static const uint64_t SideTableMask = maskForField(SideTable);
  static const size_t SideTableUnusedLowBits = 3;

  static const size_t SideTableMarkShift = SideTableBitCount;
  static const size_t SideTableMarkBitCount = 1;
  static const uint64_t SideTableMarkMask = maskForField(SideTableMark);

# define maskForField(name) (((uint64_t(1)<<name##BitCount)-1) << name##Shift)
# define shiftAfterField(name) (name##Shift + name##BitCount)

static const size_t StrongExtraRefCountShift = shiftAfterField(IsDeiniting);
  static const size_t StrongExtraRefCountBitCount = 30;

UseSlowRCShift           = StrongExtraRefCountShift  + StrongExtraRefCountBitCount
                         = IsDeinitingShift + IsDeinitingBitCount + StrongExtraRefCountBitCount
                         = UnownedRefCountShift + UnownedRefCountBitCount + IsDeinitingBitCount + StrongExtraRefCountBitCount
                         = PureSwiftDeallocShift + PureSwiftDeallocBitCount + UnownedRefCountBitCount + IsDeinitingBitCount + StrongExtraRefCountBitCount
                         = 0 + 1 + 31 + 1 + 30
                         = 63 

SideTableMarkShift       = SideTableBitCount
                         = 62
SideTableUnusedLowBits   = 3

所以初始化方法变成了:
RefCountBitsT(HeapObjectSideTableEntry* side)
    : bits((reinterpret_cast<BitsType>(side) >> 3) | (BitsType(1) << 63)  | (BitsType(1) << 62))
  {
    assert(refcountIsInline);
  }

1 << 63 和1 << 62  相当于最高位和次高位是1,从[0~63]这64位最高前两位是1
sild >> 3 表示第三位清空

OK,至此来个案例验证一下:

3、Unowned(无主引用)

类似弱引用,其不会牢牢持有实例,无主引用嘉定是永远有值。


image.png
image.png

弱引用与无主引用什么时候用呢?

如果两个对象的生命周期完全和对方没有关系,用weak
如果其中一个对象销毁,另一个对象也跟着销毁,可以用unowned
费强引用对象拥有和强引用对象同样的或者更长的声明周期,用unowned。

总结

  • 一个对象在概念上有三种引用。这些refcounts要么存储在isa后面的字段中inLine,要么存储在isa后面的字段所指向的side table entry (散列表)中。

  • 强引用:强RC计数对象的强引用(strong references)。当强RC达到零时,对象被销毁,无主引用(unowned)读取将出现错误,而弱引用(weak reference)读取将变成 nil 。强RC被存储为一个额外的计数:物理字段为 0,逻辑值为 1

  • 无主引用:无主RC计算对该对象的无主引用。无主RC也有一个额外的+1代表强引用;这个+1在deinit完成后减少。当无主RC达到零时,该对象的分配将被释放。

  • 弱引用:弱RC计数对象的弱引用。弱RC也有一个额外的+1代表无主引用;这个+1在对象的分配被释放后递减。当弱RC达到0时,对象的散列表将被释放。

  • 对象开始时是没有散列表的。当以下情形的时候散列表生成:

    1. 弱引用被形成,以及有pending future implementation
    2. 强引用溢出或无主引用溢出(内联引用在32位系统上是很小的)
      3.一个对象需要关联对象存储时
      获取散列表是一种单向操作,具有散列表的对象永远不会丢失它。这可以防止一些线程竞争。

强的和无主的变量指向这个对象。弱变量指向对象的散列表。

存储布局:

HeapObject {
   isa
   InlineRefCounts {
     atomic<InlineRefCountBits> {
       strong RC + unowned RC + flags
       OR
       HeapObjectSideTableEntry*
     }
   }
} 
  
HeapObjectSideTableEntry {
   SideTableRefCounts {
     object pointer
     atomic<SideTableRefCountBits> {
       strong RC + unowned RC + weak RC + flags
     }
   }
}
  

对象的生命周期状态:
LIVE without side table
对象的引用初始化为1个强引用,1个无主引用,1个弱引用
没有散列表,没有弱引用存储
强变量和无主变量正常工作
弱变量load不会发生
弱变量存储添加了散列表,成为LIVE with side table
当强引用计数达到0时,调用deinit()同时对象成为正在DEINITING状态

LIVE with side table
弱变量正常工作,其他的和LIVE一样

DEINITING without side table
Deinit()正在处理该对象。
强变量操作没有效果。
swift_abortretainun()中,无主变量加载停止。
无主变量存储正常工作。弱可变load不会发生。弱变量store存储nil。
deinit()完成时,生成的代码调用swift_deallocObject。而swift_deallocObject调用canBeFreedNow()快速检查没有弱的或无主的引用。如果canBeFreedNow()对象被释放并且它变成死的。否则,它会对无主RC进行减量,使该对象变成DEINITED

DEINITING with side table
弱变量加载返回nil。弱变量store存储nil。
canBeFreedNow()总是false,所以它永远不会直接过渡到DEAD
其他的都和DEINITING一样。

DEINITED without side table
Deinit()已经完成,但是还有一些无主引用。强变量操作不会发生。无主变量存储不会发生。
swift_abortretainun()中,无主变量加载停止。弱变量操作不会发生。当无主RC的值为零时,该对象将被释放,并且它将成为DEAD状态。

DEINITED with side table
弱变量加载返回nil。弱变量存储不会发生。
当无主引用变为零时,该对象将被释放,而弱引用将被释放递减,对象变成FREED。其他的都和DEINITED一样。

FREED without side table
这个状态不可能发生

FREED with side table
对象被释放,但对散列表有弱引用未被释放。
强变量操作不会发生。
无主的变量操作不会发生。
弱变量加载返回nil。
弱变量存储不会发生。
当弱引用达到零时,将释放散列表,对象变成DEAD

DEAD
对象和对象的散列表都没有了

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351

推荐阅读更多精彩内容