Swift 内存管理

Swift 内存管理

[TOC]

前言

本文将介绍一下Swift中的内存管理,关于内存的一些基础知识可以参考我以前写过的一些内存相关的文章:

iOS内存五大区
iOS 中的虚拟内存和物理内存
Mach-O探索

在以前的文章Swift中的值类型和引用类型。我们介绍了Swift中的两大数据类型,对于值类型这种数据类型直接存储的就是值,在拷贝的时候也是另外复制一份值,也就是深拷贝。对于引用类型的数据,则跟值类型不同,拷贝的时候是指针拷贝,也就是浅拷贝,此时就会涉及到对同一内存区域数据的引用,在Swift中记录这些引用采用的是与Objective-C一样的自动引用计数(Arc)机制,来追踪和管理内存。下面我们就来一起Swift中的引用计数是如何实现的。

1. 强引用

Swift类,对象这篇文章中我们知道Swift中对象的本质是HeapObject,其中有两个属性,分别是metadatarefCounts,源码如下:

// The members of the HeapObject header that are not shared by a
// standard Objective-C instance
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

/// The Swift heap-object header.
/// This must match RefCountedStructTy in IRGen.
struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *metadata;

  SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
  `
  `
  `
}

其实这个refCounts就是记录Swift引用计数的。

1.1 初步探索

首先我们通过一个简单的例子来打印一下对象的引用计数:

class Teacher {
    var age: Int = 18
    var name: String = "xiaohei"
}

var t = Teacher()
var t1 = t
var t2 = t

print("end")

按照上面的代码我们可以看出,t1t2都强持有了t这个对象,在OC中我们知道alloc的时候并不会增加对象的引用计数,只是在获取引用计数的时候会自动加1,那么Swift是怎么做的呢?

首先我们先通过lldb查看一下上述代码执行的结果。

image

首先我们从左侧直接复制t对象的地址进行内存查看,然后在通过p/x命令打印出t对象的地址后,再次进行查看。

通过这两次查看,我们得到了两个不同的结果:

  1. 显然第二段内存中存储的不是一个地址
  2. 对于引用计数也不是很符合我们的预期
    1. 创建增加引用计数应该是3
    2. 不增加是2
  3. p命令貌似会增加对象的引用计数

1.2 refCounts源码分析

带着上面的结论(疑问),我们打开Swift源码进行分析(这是一份Swift5.3.1源码)

1.2.1 refCounts

首先我们来看看refCounts

在上面的源码中我们已经能够知道refCounts是从一个宏定义来的。

// The members of the HeapObject header that are not shared by a
// standard Objective-C instance
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS       \
  InlineRefCounts refCounts

我们点击按住command键,点击InlineRefCounts跳转到RefCount.h文件中,可以看到如下代码:

typedef RefCounts InlineRefCounts

我们可以看到这是一个模板类,InlineRefCountsRefCounts的别名,模板内部需要传入InlineRefCountBits,下面我们分别来看看RefCountsInlineRefCountBits

RefCounts:

template 
class RefCounts {
    std::atomic refCounts;
// 省略将近600行代码

}

这个类有将近600行代码,我们可以看到这里面的refCountsRefCountBits类型,也就是传入的模板参数,所以说这里实际上是通过传入的模板参数进行处理一些事情。对于其他的方法都是模板类中定义的一些方法。由于代码过多,感兴趣的自己下载一份代码研究研究吧。下面我们看看InlineRefCounts

1.2.2 InlineRefCounts

返回后,点击InlineRefCounts跳转到RefCount.h文件中,可以看到如下代码:

typedef RefCountBitsT InlineRefCountBits;

首先这还是个模板类,这次我们先查看一下,传入的RefCountIsInline

1.2.3 RefCountIsInline

// RefCountIsInline: refcount stored in an object
// RefCountNotInline: refcount stored in an object's side table entry
enum RefCountInlinedness { RefCountNotInline = false, RefCountIsInline = true };

可以看到RefCountIsInline是个enumtruefalse两个枚举值,所以在此处这个传入的值并不像InlineRefCounts那样去做很多事情。所以我们就需要看看RefCountBitsT这个模板类。

1.2.4 RefCountBitsT

RefCountBitsT这个类也在RefCount.h这个文件中,将近300行代码。感兴趣的自己下载全部源码进行查看吧,这里我们挑重点分析,这里面有个属性BitsType bits,其他的都是针对这个属性的一些方法,所以我们认为这个属性很重要,那么BitsType又是什么呢?这也是个别名,我们点击跳转,其实就在其上面隔两行代码处:

typedef typename RefCountBitsInt::Type
    BitsType

这还是取别名,我们继续点击跳转,还是在RefCount.h这个文件中:

// Raw storage of refcount bits, depending on pointer size and inlinedness.
// 32-bit inline refcount is 32-bits. All others are 64-bits.

template 
struct RefCountBitsInt;

// 64-bit inline
// 64-bit out of line
template 
struct RefCountBitsInt {
  typedef uint64_t Type;
  typedef int64_t SignedType;
};

// 32-bit out of line
template <>
struct RefCountBitsInt {
  typedef uint64_t Type;
  typedef int64_t SignedType;
};

// 32-bit inline
template <>
struct RefCountBitsInt {
  typedef uint32_t Type;
  typedef int32_t SignedType;  
}

看到这里就明朗了,对于这个bits32位的就是32位,64位就是64位,鉴于现在我们使用的都是64位的系统,所以我们完全可以把它当做一个uint64_t,也就是64位整形。

至此我们对refCounts的探索基本就有结论了,其引用计数的原始存储就是个uint64_t

1.2.5 回到swift_allocObject中看初始化

接下来我们就来看看,对象初始化的时候是怎样处理refCounts的呢?此时我们来到HeapObject.cpp文件中的_swift_allocObject_方法中。

这里有这样一行代码,是初始化HeapObject的:

new (object) HeapObject(metadata);

我们点击跳转过去:

  // Initialize a HeapObject header as appropriate for a newly-allocated object.
  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }

我们可以看到这里传递了两个参数,一个是metadata,另一个是refCounts,其中refCounts是传入的一个Initialized,那么这是什么呢?我们继续点击跳转,在RefCount.h文件中可以看到如下代码:

1.2.6 Initialized & Initialized_t

enum Initialized_t { Initialized }

我们发现Initialized是一个枚举,名称为Initialized_t,我们在该文件中直接搜索一下Initialized_t,这个时候就可以找到如下代码:

// Refcount of a new object is 1.
  constexpr RefCounts(Initialized_t)
    : refCounts(RefCountBits(0, 1)) {}

此时我们可以看到这里调用的了RefCountBits的初始化方法,也就是我们最开始看源码时的模板类RefCounts传入的那个做事类型(RefCountBits),那么这个做事的RefCountBits究竟干了些什么呢?在上面的分析中我们可以知道,真正做事情的是RefCountBitsT

1.2.7 再探 RefCountBitsT

那么我们就来分析RefCountBitsT。在这个类中我们开始查找它的初始化方法,首先我们看到的就是下面这个(还有其他三个),为什么选这个呢?因为参数里有strongExtraCountunownedCount,跟引用计数可能相关。

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  constexpr
  RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
    : bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
           (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
           (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
  { }

我们看到这里是做一些偏移操作,跟OC中操作isa的时候有些类似。

1.2.8 RefCountBitOffsets

那么这些偏移都是什么呢?我们点击一个跳转定义的地方去看看,结果却没跳转过去,然后我就搜索了一下StrongExtraRefCountShift,就找到了。后面我发现点击Offsets

Offsets:

typedef RefCountBitOffsets
    Offsets

RefCountBitOffsets:

template 
struct RefCountBitOffsets;

// 64-bit inline
// 64-bit out of line
// 32-bit out of line
template <>
struct RefCountBitOffsets<8> {
  /*
   The bottom 32 bits (on 64 bit architectures, fewer on 32 bit) of the refcount
   field are effectively a union of two different configurations:
   
   ---Normal case---
   Bit 0: Does this object need to call out to the ObjC runtime for deallocation
   Bits 1-31: Unowned refcount
   
   ---Immortal case---
   All bits set, the object does not deallocate or have a refcount
   */
  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);
};

根据上面这段代码,我们可以看到,这是个位域结构,有32位和64位之分。本文主要说一下64位的,因为平常开发中都是用的64位。

1.3 refCounts 结构分析

如果只较上面的分析来说,详细的讲解这些还有点早,因为这里会涉及到sideTable,也就是散列表,熟悉oc的同学应该知道,在Objective-C中的引用计数也会存储在散列表中。下面我们就分两种情况分析一下以上代码引申出的东西。

首先我们知道refCounts主要是通过RefCountBitsT中的bits,也就是uint64_t一个64位的整形存储数据。以上代码就是64位整形中在不同情况下存储数据的含义,所占位数。

1.3.1 没有使用 SideTable 的情况

如果没有使用散列表,在Swift5.3.1源码中其实又分了两种情况(在5.2.4中是另一种情况,在以前的版本中第一位命名还有过IsPinned,这里就按照5.3.1中的分析了)其实都是64位,会有不同的含义,在各个版本中其实也是大同小异,这里以思想为主。

首先根据注释我们可以知道,在64位架构上refcount的低32位是有效的结合两种不同的配置。

在正常情况下,这段64位的整形结构中的存储会是这样的:

image
名称 含义 占位
PureSwiftDealloc 是否是纯Swift(或者调用Objc运行时) 0
UnownedRefCount 无主引用计数 1~31
IsDeiniting 是否正在释放 32
StrongExtraRefCount 强引用计数 33~62
UseSlowRC 使用缓慢RC 63

Immortal不朽(长生)的情况下,根据代码中的注释,这段是覆盖PureSwiftDeallocUnownedRefCount,结合以往的源码,这段64位的整形结构中的存储会是这样的:

image

其实这里主要是将第一位的PureSwiftDealloc替换成了IsImmortal,根据注释,是这样的情况下,如果设置了所有位,这个对象是不能被释放的或者有一个refcount的。

1.3.2 使用 SideTable 的情况

在后续的弱引用的情况下会使用到SideTable,如果使用了SideTable,这段64位的整形结构中的存储会是这样的:

image
名称 含义 占位
SideTable 是否正在释放 0~61
SideTableMark 强引用计数 62
UseSlowRC 使用缓慢RC 63

1.4 结合lldb打印分析

按照上面的一系列分析,还有一开始我们的lldb打印的结果,我们来对照分析一下。首先打开计算器,将0x0000000400000003这个16进制的值粘贴进去,结果如下:

image

我们可以看到此时的结果:

  • 第0位是1,所以说这是个纯Swift的对象。

  • 第1位也是1,说明UnownedRefCount是1。

  • 第34位也是1,说明StrongExtraRefCount是2,二进制的10位10进制的2

  • 所以在1.1中的对象t的强引用计数是2。

  • 那么说明在Swift对象创建的时候,与OC是一致的,都不会增加强引用计数的。

1.1中如果我们使用p命令打印了对象,在读取内存段的时候,其内存段会发生变化,在1.1中是变成了0x0000000600000003,转换成2进制如下:

image

可以明显的看到,此时强引用计数是增加了1的。所以我们试着通过print打印一下,看看什么结果。

image

我们分别在第一个断点前后查看了一下t对象的内存段,发现并没有引用计数的变化。这是怎么回事呢?其实print函数也会增加对象的引用计数,也就是调用swift_retain,这点我们可以通过汇编代码验证:

image

在调用print函数前,调用了一次swift_retain,通过内存结构我们可以看出调用swift_retain的对象正式t,这里使用的是x86汇编,这里的rax寄存器中的地址与左侧的t对象的内存地址是一样的。

那么为什么调用print前后我们没有通过内存段看出强引用计数的增加呢?我觉得其内部应该是会进行release的,虽然我没能够验证,但是我查了查网上的资料,有人也是这么说的,而且p命令在lldb调试阶段为了保证调试中不中断,所以增加了对象的引用计数也是可以理解的。暂且就这么认为吧。所以说:

  • 通过print函数打印对象会调用对象retainrelease
  • 通过p命令在lldb中打印对象会增加对象的引用计数

1.5 无主引用 unowned

首先说一下什么是unowned,在Swift中有Strongweakunowned三种引用方式,unowned属于弱引用的一种,相较于weak来说,unowned会用在你明确这个对象不是nil的时候,也就是说weak修饰的对象会强制为可选类型optional,而unowned不会。

按照苹果官方文档的说法就是如果你能确定这个引用对象一旦被初始化之后就永远不会变成nil,那就用unowned;不是的话,就用weak。

我们创建对象的时候无主引用的值就是1,这个1可以通过HeapObject的初始化方法中看到:

// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata) 
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
  
enum Initialized_t { Initialized }
  
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) { }

LLVM_ATTRIBUTE_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
       (BitsType(1)                << Offsets::PureSwiftDeallocShift) |
       (BitsType(unownedCount)     << Offsets::UnownedRefCountShift))
{ }

现在把这几段代码放到一起就很清晰了:

  • 初始化对象的时候并不会有强引用,传入的是0
  • 初始化对象的时候无主引用为1,因为传入的就是1

当你使用unowned关键字的时候就会增加无主引用计数:

image

在断点前后我们通过lldb打印可以看出,在使用unowned关键字后,无主引用计数的增加。

一般我们很少会用到unowned,这里建议在init方法中涉及的循环引用处使用unowned

先提一下,对于对象的释放,在OC中当引用计数为0时即会释放该对象,在Swift中,还需要unowned也为0时才会释放对象。关于这个的解释,可以参考stackoverflow上的这篇文章。这是我觉得解释的特别好的。

1.6 引用计数是如何增加的

通过上面的分析我们知道Swift中的强引用计数会在对象拷贝的时候增加,还是这段代码:

var t = Teacher()
var t1 = t

我们通过Sil代码看看t1 = t的时候做了什么操作。

编译成sil代码的命令

rm -rf main.sil && swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil && open main.sil
image

sil代码中我们可以看到在对象拷贝的时候调用了copy_addr命令,那么这个命令都做了什么呢?我们打开SIL参考文档,找到copy_addr

image

我们可以看到copy_addr中会调用strong_retainstrong_retain就是Swift中的swift_retain

1.6.1 swift_retain

swift_retain实际调用的是_swift_retain_,这个是由一个内部宏定义得到了的,感兴趣的可以查看一下CALL_IMPL这个宏。

HeapObject *swift::swift_retain(HeapObject *object) {
  CALL_IMPL(swift_retain, (object));
}

static HeapObject *_swift_retain_(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
  if (isValidPointerForNativeRetain(object))
    object->refCounts.increment(1);
  return object;
}

_swift_retain_方法中,会调用increment方法来增加refCounts

increment:

  // Increment the reference count.
  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;
      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));
  }

increment方法分析:

  1. 首先根据refCounts取出原始的oldbits
  2. 然后判断增加的计数不为1,并且是不朽对象,则直接返回
  3. 通过一个do while循环处理引用计数加1的操作
    1. 通过incrementStrongExtraRefCount函数对引用计数增加
    2. 返回值如果是快速的结果则为true,否则为false
    3. false的时候是设置了UseSlowRC或者引用计数溢出了
    4. 如果不是快速的,判断不是不朽对象后则直接返回
    5. 如果是则调用incrementSlow函数进一步处理
    6. 循环的条件是新旧bits的内存结构是否一致在取反
    7. 如果上面修改成功了,则内存结构不一致,直接结束循环,如果一致则进入循环再次增加,这里应该是避免多线程操作时引用计数时取值不对的问题

incrementStrongExtraRefCount:

// Returns true if the increment is a fast-path result.
// Returns false if the increment should fall back to some slow path
// (for example, because UseSlowRC is set or because the refcount overflowed).
LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
bool incrementStrongExtraRefCount(uint32_t inc) {
// This deliberately overflows into the UseSlowRC field.
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}
  1. 该方法是实现强引用计数增加的
  2. 增加时是左移StrongExtraRefCountShift位(33位,从0开始计算)
  3. 返回值是判断增加完的引用计数位是否大于等于0
    1. 是的话就是快速的
    2. 不是就是慢速的,慢速的可能需要借助散列表进行处理,也有可能设置了UseSlowRC

incrementSlow:

template 
void RefCounts::incrementSlow(RefCountBits oldbits,
                                            uint32_t n) {
  if (oldbits.isImmortal(false)) {
    return;
  }
  else if (oldbits.hasSideTable()) {
    // Out-of-line slow path.
    auto side = oldbits.getSideTable();
    side->incrementStrong(n);
  }
  else {
    // Retain count overflow.
    swift::swift_abortRetainOverflow();
  }
}
  1. 该函数主要是三层判断isImmortal(false)直接返回
  2. 使用散列表则通过散列表进行增加强引用计数
  3. 其余的则Retain count overflow.

1.6.2 swift_unownedRetain

这里在简单的介绍一下swift_unownedRetain,原理跟swift_retain差不多。

swift_unownedRetain:

HeapObject *swift::swift_unownedRetain(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_unownedRetain);
  if (!isValidPointerForNativeRetain(object))
    return object;

  object->refCounts.incrementUnowned(1);
  return object;
}

incrementUnowned:

  // UNOWNED
  
  public:
  // Increment the unowned reference count.
  void incrementUnowned(uint32_t inc) {
    auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
    if (oldbits.isImmortal(true))
      return;
    RefCountBits newbits;
    do {
      if (oldbits.hasSideTable())
        return oldbits.getSideTable()->incrementUnowned(inc);

      newbits = oldbits;
      assert(newbits.getUnownedRefCount() != 0);
      uint32_t oldValue = newbits.incrementUnownedRefCount(inc);

      // Check overflow and use the side table on overflow.
      if (newbits.isOverflowingUnownedRefCount(oldValue, inc))
        return incrementUnownedSlow(inc);

    } while (!refCounts.compare_exchange_weak(oldbits, newbits,
                                              std::memory_order_relaxed));
  }

incrementUnownedRefCount:

  // Returns the old reference count before the increment.
  LLVM_ATTRIBUTE_ALWAYS_INLINE
  uint32_t incrementUnownedRefCount(uint32_t inc) {
    uint32_t old = getUnownedRefCount();
    setUnownedRefCount(old + inc);
    return old;
  }

原理上差不多的,这里就不过多解释了,感兴趣的可以看看源码,也可以通过源码进行断点调试,调试的时候会有很多情况调用到上述的一些方法,此时需要注意一下metadata是不是我们定义的对象。

1.7 小结

对于Swift中对象的强引用在上面说了许多,但是乍一看也不是很好理解,总结一下吧。

  1. Swift同样使用ARC自动引用计数管理对象
  2. 对于强引用计数通过refCounts进行记录
  3. refCounts是由很多模板类定义的,在纯Swift中主要做事的是RefCountBitsT
  4. refCounts实质是RefCountBitsT中的bits,也就是uint64_t是一个64位的整形
  5. 通常情况下64位整形的第0位标记是否是纯Swift,否则就是调用objc运行时处理
  6. 第1到31位是存储无主引用计数的
  7. 无主引用计数unownedSwift中弱引用的一种
  8. unowned通常用在确定不为空的防止循环引用的地方(例如init方法中的闭包里)
  9. Swift对象初始化的是默认强引用计数为0,无主引用计数为1
  10. 当拷贝对象时会调用swift_retain方法增加对象的引用计数
  11. 使用unowned修饰对象是会使无主引用计数加1,实际是调用的swift_unownedRetain方法增加引用计数的
  12. swift中对象的释放是在强引用和无主引用都为0的时候
  13. 如果类继承NSObject就会使用OC的内存管理,感兴趣的可以看看OC内存管理相关知识
  14. 对于弱引用和上面提到的散列表我们在下面的篇章会详细的说明

2. 弱引用

下面我们来说一下弱引用,在Swift中弱引用分为两种,一种是我们熟知的weak,另一种就是unowned,关于unowned在上一节强引用中已经做了详细的介绍,这里我们主要说一说weak

2.1 弱引用示例

还是举个例子:

var t = Teacher()
weak var t1 = t
image

我们可以看到在第一个断点前后分别查看t对象的内存段会有明显的区别。这不仅仅是增加计数那么简单,那么这是为什么呢?

2.2 汇编代码分析

带着上一节的疑问,我们就看看汇编代码:

image

我们看到在将t对象赋值给t1的时候,首先是个可选类型optional,然后会调用swift_weakInit方法,那么这个swift_weakInit做了些什么呢?

2.3 源码分析

2.3.1 swift_weakInit

HeapObject.cpp文件中我们可以找到swift_weakInit的源码:

WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
  ref->nativeInit(value);
  return ref;
}

swift_weakInit方法中,有两个参数,第二个参数是HeapObject,也就是Swift中的对象,那么第一个参数是什么呢?我们点击跳转过去看看。

2.3.2 WeakReference

WeakReference.h文件中,我们可以看到WeakReference是一个类,有200多行代码,主要是由两个属性,一个是不与OC交互时用的,另一个是与OC交互时用的,其余方法基本都是操作这两个属性的,属性源码如下:

class WeakReference {
  union {
    std::atomic nativeValue;
#if SWIFT_OBJC_INTEROP
    id nonnativeValue;
#endif
  }
 
 `
 `
 ` 
}

2.3.3 WeakReferenceBits

属性中的WeakReferenceBits也是个类,同样在WeakReference.h文件中,这里面有个枚举,还有个uintptr_t bits;属性和一下操作属性的方法。,代码也比较多,就不粘贴了。

2.3.4 nativeInit

swift_weakInit方法中方法,会继续调用nativeInit方法。

nativeInit:

void nativeInit(HeapObject *object) {
    auto side = object ? object->refCounts.formWeakReference() : nullptr;
    nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}

nativeInit方法中首先是获取一个散列表,然后存储引用计数。获取散列表通过formWeakReference方法,在RefCount.cpp文件中。

2.3.5 formWeakReference

// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts::formWeakReference()
{
  auto side = allocateSideTable(true);
  if (side)
    return side->incrementWeak();
  else
    return nullptr;
}

首先这里会调用allocateSideTable方法获取到散列表,也在RefCount.cpp文件中。

2.3.6 allocateSideTable

// Return an object's side table, allocating it if necessary.
// Returns null if the object is deiniting.
// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts::allocateSideTable(bool failIfDeiniting)
{
  auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
  
  // Preflight failures before allocating a new side table.
  if (oldbits.hasSideTable()) {
    // Already have a side table. Return it.
    return oldbits.getSideTable();
  } 
  else if (failIfDeiniting && oldbits.getIsDeiniting()) {
    // Already past the start of deinit. Do nothing.
    return nullptr;
  }

  // Preflight passed. Allocate a side table.
  
  // FIXME: custom side table allocator
  HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  
  auto newbits = InlineRefCountBits(side);
  
  do {
    if (oldbits.hasSideTable()) {
      // Already have a side table. Return it and delete ours.
      // 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;
}
  1. 首先获取到原有的引用计数oldbits
  2. 如果oldbits中已经存在SideTable,则直接返回
  3. 如果正在销毁这个对象,则返回空指针
  4. 如果以上都没有,则直接初始化一个new一个新的SideTable
  5. 然后把新创建的side作为参数初始化InlineRefCountBits,也就是在分析强引用是提到的其他三个初始化方法中的另一个(后面会接着分析)
  6. 接下来是通过一个do while循环来处理,循环的条件是比较交换新旧bits内存是否成功,在取反,在do中:
    1. 还是先判断了一下旧的bits中是否有sideTable,如果有就删除刚刚初始化的,返回旧的
    2. 如果没有则再次判断是否正在销毁,是的话则返回空指针
    3. 将原有弱引用计数存储在sidetable
  7. 返回side

2.3.7 RefCountBitsT 的另一个初始化方法

接下来我们要先看看InlineRefCountBits(side)涉及到的初始化方法,点击跳转过去:

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

这个在强引用处我们分析了几种不同refCounts结构,这种就是包括散列表的那种。这里直接把散列表右移SideTableUnusedLowBits也就是3位,存储在64位整形的前62位。最后两位全部置为1。所以这也就是我们一开查看t对象内存段时相较于只有一个强引用时refCounts段差距较大的原因,本来只存了一个强引用计数,现在是存储地址,所以差距较大。

2.3.8 HeapObjectSideTableEntry

接下来我们看看,在各处都出现的这个HeapObjectSideTableEntry是什么,点击跳转过去:

class HeapObjectSideTableEntry {
  // FIXME: does object need to be atomic?
  std::atomic object;
  SideTableRefCounts refCounts;
`
`
`  
}

这也是个类,里面包含两个属性,一个是记录当前对象,另一个是refCounts,类型是SideTableRefCounts

2.3.9 SideTableRefCounts & SideTableRefCountBits

typedef RefCounts SideTableRefCounts

SideTableRefCounts是取的别名,点击SideTableRefCountBits跳转过去,在RefCount.h文件中。

class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT
{
  uint32_t weakBits;
  `
  `
  `
}

我们可以看到SideTableRefCountBits是个类,继承自RefCountBitsT,所以说SideTableRefCountBits会同时具有个64位整形的bits和32位的weakBits。一个用于存储原有的强引用和无主引用,新增的存储弱引用。

2.3.10 incrementWeak

分析了一大堆,我们该返回一下了,在formWeakReference方法中,获取到side之后,判断有值就会调用incrementWeak方法。

  LLVM_NODISCARD
  HeapObjectSideTableEntry* incrementWeak() {
    // incrementWeak need not be atomic w.r.t. concurrent deinit initiation.
    // The client can't actually get a reference to the object without
    // going through tryRetain(). tryRetain is the one that needs to be
    // atomic w.r.t. concurrent deinit initiation.
    // The check here is merely an optimization.
    if (refCounts.isDeiniting())
      return nullptr;
    refCounts.incrementWeak();
    return this;
  }

incrementWeak:

  // Increment the weak reference count.
  void incrementWeak() {
    auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
    RefCountBits newbits;
    do {
      newbits = oldbits;
      assert(newbits.getWeakRefCount() != 0);
      newbits.incrementWeakRefCount();
      
      if (newbits.getWeakRefCount() < oldbits.getWeakRefCount())
        swift_abortWeakRetainOverflow();
    } while (!refCounts.compare_exchange_weak(oldbits, newbits,
                                              std::memory_order_relaxed));
  }

这里跟上面分析过的代码思想差不多,这次只看主要代码:

incrementWeakRefCount

 LLVM_ATTRIBUTE_ALWAYS_INLINE
  void incrementWeakRefCount() {
    weakBits++;
}

这里就很简单了,直接就是weakBits++

2.4 验证上述代码

至此我们对源码的探索也差不多了,那么在一开始的lldb打印的时候那个内存段,我们根据源码的思想来验证一下。重新运行了一下:

image

0xc0000000200c7e8a,首先去掉最后两位,就变成了0x200C7E8A,在按照初始化的时右移了三位,这回我们左移三位将其变回原来的值,即0x10063F450。这个值我是用计算器算的。读取该地址的内存结构,结果如下图:

image

这这段内存实际是个HeapObjectSideTableEntry,由HeapObject*SideTableRefCounts两个类型的属性组成,所以:

  • 第一个8字节是实例对象的内存地址
  • 第二个8字节应该是内存对齐空出来的
  • 第三个8字节存储的是64位整形,存放原对象中原有的强引用计数和无主引用计数等
  • 第四个8字节中使用32位存储弱引用计数

到了这里还有个疑问,为什么只有一个弱引用,但是这里弱引用计数是2呢?这了一会,我发现如下代码和注释,这段代码在RefCount.h这个文件的SideTableRefCountBits类中。

  LLVM_ATTRIBUTE_ALWAYS_INLINE
  constexpr
  SideTableRefCountBits(uint32_t strongExtraCount, uint32_t unownedCount)
    : RefCountBitsT(strongExtraCount, unownedCount)
    // weak refcount starts at 1 on behalf of the unowned count
    , weakBits(1)
  { }

根据内部注释,这里的弱引用计数从1开始,代表有一个无主引用计数。所以在刚刚的内存段查看的时候为什么是2也就解释清楚了。

2.5 再次进行强引用

当我们使用弱引用后就会产生一个sideTable用于存储弱引用计数,而之前记录强引用的refCounts字段也会成为存储sideTable指针的作用。那么有了弱引用后,再次增加强引用,这个强引用是怎么存储的呢?下面我们就来看看。

这个在1.6.1我们就有了相关的介绍,前的步骤都是一致的,强引用的增加同样会调用swit_retain,但是因为此时sideTable的存在就会在sideTable中增加强引用计数。

incrementSlow:

template 
void RefCounts::incrementSlow(RefCountBits oldbits,
                                            uint32_t n) {
  if (oldbits.isImmortal(false)) {
    return;
  }
  else if (oldbits.hasSideTable()) {
    // Out-of-line slow path.
    auto side = oldbits.getSideTable();
    side->incrementStrong(n);
  }
  else {
    // Retain count overflow.
    swift::swift_abortRetainOverflow();
  }
}

RefCount.h文件中我们可以找到incrementStrong方法。

void incrementStrong(uint32_t inc) {
    refCounts.increment(inc);
}

同样会继续调用increment方法,这个在1.6.1中也介绍过了,其实还是操作那个64位的整形,只是在不同的地方而已,原来是HeapObjectrefCounts现在是HeapObjectSideTableEntry->SideTableRefCounts->refCounts

对于再次使用无主引用的时候也是如此,这里就不过多介绍了。

对于原来有强引用,在上面介绍的时候已经说过,在生成sideTable的时候,会存储旧的引用计数的。

2.6 小结

至此我们对swift中的弱引用的分析基本就完毕了,在swift中的弱引用对于HeapObject来说,其refConuts有两种:

  1. 无弱引用的时候:strongCount+unownedConut
  2. 有弱引用的时候:指向HeapObjectSideTableEntry的指针
    1. 指针的获取需要去掉两个最高位
    2. 然后在左移三位
  3. HeapObjectSideTableEntry的存储结构如下:
    1. HeapObject *object
    2. xxx(不知道是啥)
    3. strong Count + unowned Count(uint64_t)//64位
    4. weak count(uint32_t)//32位

可以理解为如下代码:

HeapObject {
    InlineRefCountBit {strong count + unowned count }
    
    HeapObjectSideTableEntry{
        HeapObject *object
        xxx
        strong Count + unowned Count(uint64_t)//64位
        weak count(uint32_t)//32位
    }
}

3. swift_release

我们都知道有retain就会有release,那么在Swift中的release是怎么样的呢?下面我们就来看看。

3.1 swift_release

这里我们就直接搜索swift_release,找到他的源码,在HeapObject.cpp文件中。

static void _swift_release_(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_release);
  if (isValidPointerForNativeRetain(object))
    object->refCounts.decrementAndMaybeDeinit(1);
}

void swift::swift_release(HeapObject *object) {
  CALL_IMPL(swift_release, (object));
}

3.2 decrementAndMaybeDeinit

接下来我们点击``跳转到下面代码处:

// Decrement the reference count.
// Return true if the caller should now deinit the object.
LLVM_ATTRIBUTE_ALWAYS_INLINE
bool decrementShouldDeinit(uint32_t dec) {
    return doDecrement(dec);
}

enum PerformDeinit { DontPerformDeinit = false, DoPerformDeinit = true }

没什么好说的,就是一层包装调用

3.3 doDecrement

  // Fast path of atomic strong decrement.
  // 
  // Deinit is optionally handled directly instead of always deferring to 
  // the caller because the compiler can optimize this arrangement better.
  template 
  bool doDecrement(uint32_t dec) {
    auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
    RefCountBits newbits;
    
    // constant propagation will remove this in swift_release, it should only
    // be present in swift_release_n
    if (dec != 1 && oldbits.isImmortal(true)) {
      return false;
    }
    
    do {
      newbits = oldbits;
      bool fast =
        newbits.decrementStrongExtraRefCount(dec);
      if (SWIFT_UNLIKELY(!fast)) {
        if (oldbits.isImmortal(false)) {
            return false;
        }
        // Slow paths include side table; deinit; underflow
        return doDecrementSlow(oldbits, dec);
      }
    } while (!refCounts.compare_exchange_weak(oldbits, newbits,
                                              std::memory_order_release,
                                              std::memory_order_relaxed));

    return false;  // don't deinit
}

这个方法跟上面几个方法写的思想一模一样。就不仔细分析了,这里面主要看一下decrementStrongExtraRefCountdoDecrementSlow

3.4 decrementStrongExtraRefCount

  // Returns false if the decrement should fall back to some slow path
  // (for example, because UseSlowRC is set
  // or because the refcount is now zero and should deinit).
  LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
  bool decrementStrongExtraRefCount(uint32_t dec) {
#ifndef NDEBUG
    if (!hasSideTable() && !isImmortal(false)) {
      // Can't check these assertions with side table present.

      if (getIsDeiniting())
        assert(getStrongExtraRefCount() >= dec  &&
               "releasing reference whose refcount is already zero");
      else
        assert(getStrongExtraRefCount() + 1 >= dec  &&
               "releasing reference whose refcount is already zero");
    }
#endif

    // This deliberately underflows by borrowing from the UseSlowRC field.
    bits -= BitsType(dec) << Offsets::StrongExtraRefCountShift;
    return (SignedBitsType(bits) >= 0);
  }

这里根据位移操作对引用计数进行减操作,最后减的结果是否大于等于返回。其实这里就是当做纯swift去衰减引用计数,如果出错,或者减成0以下了,则说明使用了UseSlowRC或其他慢速路径,返回false去做其他处理,否则返回true

3.5 doDecrementNonAtomicSlow

 // First slow path of doDecrementNonAtomic, where the object may need to be deinited.
  // Side table is handled in the second slow path, doDecrementNonAtomicSideTable().
  template 
  bool doDecrementNonAtomicSlow(RefCountBits oldbits, uint32_t dec) {
    bool deinitNow;
    auto newbits = oldbits;
    
    // constant propagation will remove this in swift_release, it should only
    // be present in swift_release_n
    if (dec != 1 && oldbits.isImmortal(true)) {
      return false;
    }

    bool fast =
      newbits.decrementStrongExtraRefCount(dec);
    if (fast) {
      // Decrement completed normally. New refcount is not zero.
      deinitNow = false;
    }
    else if (oldbits.isImmortal(false)) {
      return false;
    }
    else if (oldbits.hasSideTable()) {
      // Decrement failed because we're on some other slow path.
      return doDecrementNonAtomicSideTable(oldbits, dec);
    }
    else {
      // Decrement underflowed. Begin deinit.
      // LIVE -> DEINITING
      deinitNow = true;
      assert(!oldbits.getIsDeiniting());  // FIXME: make this an error?
      newbits = oldbits;  // Undo failed decrement of newbits.
      newbits.setStrongExtraRefCount(0);
      newbits.setIsDeiniting(true);
    }
    refCounts.store(newbits, std::memory_order_relaxed);
    if (performDeinit && deinitNow) {
      _swift_release_dealloc(getHeapObject());
    }

    return deinitNow;
  }

这个方法分几种情况来处理:

  1. 首先还是是否快速路径,是的话将deinitNow变量标记为false,这里也就是衰减正常完成,新的引用计数不为0
  2. 如果是不朽对象之间返回false
  3. 如果是使用sideTable,则调用doDecrementNonAtomicSideTable进行返回
  4. 到了这里就是deinit
    1. 标记deinitNowtrue
    2. 将强引用计数置为0
    3. 标记当为正在析构的状态
  5. 这里会将newbits存储到refCounts,(包括正常衰减不为0和deinit的时候)
  6. 接下来判断performDeinit && deinitNow调用_swift_release_dealloc方法
  7. 最后返回deinitNow

3.6 散列表release

template <>
template 
inline bool RefCounts::
doDecrementNonAtomicSideTable(InlineRefCountBits oldbits, uint32_t dec) {
  auto side = oldbits.getSideTable();
  return side->decrementNonAtomicStrong(dec);
}

template 
bool decrementNonAtomicStrong(uint32_t dec) {
    return refCounts.doDecrementNonAtomic(dec);
}

// Out-of-line version of non-atomic strong decrement.
// This version needs to be atomic because of the 
// threat of concurrent read of a weak reference.
template <>
template 
inline bool RefCounts::
doDecrementNonAtomic(uint32_t dec) {
  return doDecrement(dec);
}

经过几次调用,我们发现再次调用回了doDecrement方法。这是处理sideTable里面的强引用。

3.7 小结

关于swiftrelease基本就分析这么多了做个总结:

  1. 这里首先会当做正常的纯swift去做引用计数的衰减,如果衰减为0以下,则进行其他处理,否则就是正常衰减
  2. 对于正常衰减的会重置newBits
  3. 如果减没了就要deinit
  4. deinit时会调用_swift_release_dealloc方法去做析构处理

4. dealloc

如果release到没有了,就会触发dealloc操作,在上面的分析release的时候有提到过。此处会调用_swift_release_dealloc方法。

4.1 _swift_release_dealloc

void _swift_release_dealloc(HeapObject *object) {
  asFullMetadata(object->metadata)->destroy(object);
}

这个destroy,跳转过去是这样的:

/// The prefix on a heap metadata.
template 
struct TargetHeapMetadataHeaderPrefix {
  /// Destroy the object, returning the allocated size of the object
  /// or 0 if the object shouldn't be deallocated.
  TargetSignedPointer destroy;
};

到了这里仿佛线索就断了,一时不知该如何探索。但是这个方法是MetaData的方法,所以就在metadata.h中搜索了一番,找到如下可能相关的代码:

/// A function for destroying instance variables, used to clean up after an
/// early return from a constructor. If null, no clean up will be performed
/// and all ivars must be trivial.
TargetSignedPointer IVarDestroyer;

/// The heap-destructor function.
TargetRelativeDirectPointer Destroy;

/// The ivar-destructor function.
TargetRelativeDirectPointer
IVarDestroyer;

总的来说发现了ClassIVarDestroyer相关的东西,于是联想到一些方法列表的相关的。

于是又在metadata.cpp中搜索了一下,又找到如下可能相关的代码:

// Heap destructor.
fullMetadata->destroy = pattern->Destroy.get()

// I-var destroyer.
metadata->IVarDestroyer = pattern->IVarDestroyer;

我们点击那个get方法进行跳转:

typename super::PointerTy get() const & {
    auto ptr = this->super::get();
#if SWIFT_PTRAUTH
    if (Nullable && !ptr)
      return ptr;
    return ptrauth_sign_unauthenticated(ptr, ptrauth_key_function_pointer, 0);
#else
    return ptr;
#endif
}

继续点击get进行跳转:

PointerTy get() const & {
    // Check for null.
    if (Nullable && RelativeOffset == 0)
      return nullptr;
    
    // The value is addressed relative to `this`.
    uintptr_t absolute = detail::applyRelativeOffset(this, RelativeOffset);
    return reinterpret_cast(absolute);
}

到了这里我感觉是通过指针偏移去找到一个方法,然后通过指针直接调用这个方法。那么究竟调用了那个方法呢?我们这里通过汇编代码看一看

4.2 通过汇编分析

编写如下代码:

class Teacher {}

do {
    var t = Teacher()
}

在作用域结束后就会release,我们添加swift_release_dealloc符号断点,运行。

image

call处添加断点,过掉上个断点就会来到call处,点击step into

image

此时我们可以看到swift_deallocClassInstance方法的调用。然后我们就可以回调源码中搜索这个方法了。再跟汇编也没能看到其他的。

4.3 swift_deallocClassInstance

在源码的HeapObject.cpp文件中找到了swift_deallocClassInstance方法,源码如下:

void swift::swift_deallocClassInstance(HeapObject *object,
                                       size_t allocatedSize,
                                       size_t allocatedAlignMask) {
  
#if SWIFT_OBJC_INTEROP
  // We need to let the ObjC runtime clean up any associated objects or weak
  // references associated with this object.
  const bool fastDeallocSupported = SWIFT_LAZY_CONSTANT(_check_fast_dealloc());
  if (!fastDeallocSupported || !object->refCounts.getPureSwiftDeallocation()) {
    objc_destructInstance((id)object);
  }
#endif
  swift_deallocObject(object, allocatedSize, allocatedAlignMask);
}

我们可以看到,对于SwiftOC交互的时候进行额外的处理。这里主要是需要让OC运行时去处理关联对象和弱引用。然后会调用swift_deallocObject方法。

void swift::swift_deallocObject(HeapObject *object, size_t allocatedSize,
                                size_t allocatedAlignMask) {
  swift_deallocObjectImpl(object, allocatedSize, allocatedAlignMask, true);
}

接下来会调用swift_deallocObjectImpl方法

static inline void swift_deallocObjectImpl(HeapObject *object,
                                           size_t allocatedSize,
                                           size_t allocatedAlignMask,
                                           bool isDeiniting) {
  assert(isAlignmentMask(allocatedAlignMask));
  if (!isDeiniting) {
    assert(object->refCounts.isUniquelyReferenced());
    object->refCounts.decrementFromOneNonAtomic();
  }
  assert(object->refCounts.isDeiniting());
  SWIFT_RT_TRACK_INVOCATION(object, swift_deallocObject);
#ifdef SWIFT_RUNTIME_CLOBBER_FREED_OBJECTS
  memset_pattern8((uint8_t *)object + sizeof(HeapObject),
                  "\xAB\xAD\x1D\xEA\xF4\xEE\xD0\bB9",
                  allocatedSize - sizeof(HeapObject));
#endif

  // If we are tracking leaks, stop tracking this object.
  SWIFT_LEAKS_STOP_TRACKING_OBJECT(object);


  // Drop the initial weak retain of the object.
  //
  // If the outstanding weak retain count is 1 (i.e. only the initial
  // weak retain), we can immediately call swift_slowDealloc.  This is
  // useful both as a way to eliminate an unnecessary atomic
  // operation, and as a way to avoid calling swift_unownedRelease on an
  // object that might be a class object, which simplifies the logic
  // required in swift_unownedRelease for determining the size of the
  // object.
  //
  // If we see that there is an outstanding weak retain of the object,
  // we need to fall back on swift_release, because it's possible for
  // us to race against a weak retain or a weak release.  But if the
  // outstanding weak retain count is 1, then anyone attempting to
  // increase the weak reference count is inherently racing against
  // deallocation and thus in undefined-behavior territory.  And
  // we can even do this with a normal load!  Here's why:
  //
  // 1. There is an invariant that, if the strong reference count
  // is > 0, then the weak reference count is > 1.
  //
  // 2. The above lets us say simply that, in the absence of
  // races, once a reference count reaches 0, there are no points
  // which happen-after where the reference count is > 0.
  //
  // 3. To not race, a strong retain must happen-before a point
  // where the strong reference count is > 0, and a weak retain
  // must happen-before a point where the weak reference count
  // is > 0.
  //
  // 4. Changes to either the strong and weak reference counts occur
  // in a total order with respect to each other.  This can
  // potentially be done with a weaker memory ordering than
  // sequentially consistent if the architecture provides stronger
  // ordering for memory guaranteed to be co-allocated on a cache
  // line (which the reference count fields are).
  //
  // 5. This function happens-after a point where the strong
  // reference count was 0.
  //
  // 6. Therefore, if a normal load in this function sees a weak
  // reference count of 1, it cannot be racing with a weak retain
  // that is not racing with deallocation:
  //
  //   - A weak retain must happen-before a point where the weak
  //     reference count is > 0.
  //
  //   - This function logically decrements the weak reference
  //     count.  If it is possible for it to see a weak reference
  //     count of 1, then at the end of this function, the
  //     weak reference count will logically be 0.
  //
  //   - There can be no points after that point where the
  //     weak reference count will be > 0.
  //
  //   - Therefore either the weak retain must happen-before this
  //     function, or this function cannot see a weak reference
  //     count of 1, or there is a race.
  //
  // Note that it is okay for there to be a race involving a weak
  // *release* which happens after the strong reference count drops to
  // 0.  However, this is harmless: if our load fails to see the
  // release, we will fall back on swift_unownedRelease, which does an
  // atomic decrement (and has the ability to reconstruct
  // allocatedSize and allocatedAlignMask).
  //
  // Note: This shortcut is NOT an optimization.
  // Some allocations passed to swift_deallocObject() are not compatible
  // with swift_unownedRelease() because they do not have ClassMetadata.

  if (object->refCounts.canBeFreedNow()) {
    // object state DEINITING -> DEAD
    swift_slowDealloc(object, allocatedSize, allocatedAlignMask);
  } else {
    // object state DEINITING -> DEINITED
    swift_unownedRelease(object);
  }
}

使用swift_slowDealloc释放内存。

// Unknown alignment is specified by passing alignMask == ~(size_t(0)), forcing
// the AlignedFree deallocation path for unknown alignment. The memory
// deallocated with unknown alignment must have been allocated with either
// "default" alignment, or alignment > _swift_MinAllocationAlignment, to
// guarantee that it was allocated with AlignedAlloc.
//
// The standard library assumes the following behavior:
//
// For alignMask > (_minAllocationAlignment-1)
// i.e. alignment == 0 || alignment > _minAllocationAlignment:
//   The runtime must use AlignedFree.
//
// For alignMask <= (_minAllocationAlignment-1)
// i.e. 0 < alignment <= _minAllocationAlignment:
//   The runtime may use either `free` or AlignedFree as long as it is
//   consistent with allocation with the same alignment.
void swift::swift_slowDealloc(void *ptr, size_t bytes, size_t alignMask) {
  if (alignMask <= MALLOC_ALIGN_MASK) {
#if defined(__APPLE__)
    malloc_zone_free(DEFAULT_ZONE(), ptr);
#else
    free(ptr);
#endif
  } else {
    AlignedFree(ptr);
  }
}

调用swift_unownedRelease处理无主引用的release,所以说swift对象的析构,是要考虑无主引用计数的。

void swift::swift_unownedRelease(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_unownedRelease);
  if (!isValidPointerForNativeRetain(object))
    return;

  // Only class objects can be unowned-retained and unowned-released.
  assert(object->metadata->isClassObject());
  assert(static_cast(object->metadata)->isTypeMetadata());
  
  if (object->refCounts.decrementUnownedShouldFree(1)) {
    auto classMetadata = static_cast(object->metadata);
    
    swift_slowDealloc(object, classMetadata->getInstanceSize(),
                      classMetadata->getInstanceAlignMask());
  }
}

在无主引用计数release完成后同样会调用swift_slowDealloc方法进行内存的释放。

关于无主引用计数的release其思想与强引用类似,会调用decrementUnownedShouldFree方法,感兴趣的可以自己去跟一跟源码,这里就不在过多的粘贴源码了。

4.4 小结

关于swiftdealloc基本就分析完毕了,现在总结如下:

  1. swiftdealloc首先会调用_swift_release_dealloc方法
  2. 关于destroy上面做了些分析,最后我觉得是通过内存偏移找到的其他方法,最终调用到swift_deallocClassInstance
  3. 析构的时候主要是释放内存
  4. 如果还存在无主引用,就会优先处理无主引用
  5. 处理完毕就会释放内存
  6. 所以swift的析构并不是强引用衰减完毕,还要处理无主引用

你可能感兴趣的:(Swift 内存管理)