Swift 内存管理
[TOC]
前言
本文将介绍一下Swift
中的内存管理,关于内存的一些基础知识可以参考我以前写过的一些内存相关的文章:
iOS内存五大区
iOS 中的虚拟内存和物理内存
Mach-O探索
在以前的文章Swift中的值类型和引用类型。我们介绍了Swift
中的两大数据类型,对于值类型这种数据类型直接存储的就是值,在拷贝的时候也是另外复制一份值,也就是深拷贝。对于引用类型的数据,则跟值类型不同,拷贝的时候是指针拷贝,也就是浅拷贝,此时就会涉及到对同一内存区域数据的引用,在Swift
中记录这些引用采用的是与Objective-C
一样的自动引用计数(Arc)机制,来追踪和管理内存。下面我们就来一起Swift
中的引用计数是如何实现的。
1. 强引用
在Swift
类,对象这篇文章中我们知道Swift
中对象的本质是HeapObject
,其中有两个属性,分别是metadata
和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
/// 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")
按照上面的代码我们可以看出,t1
和t2
都强持有了t
这个对象,在OC
中我们知道alloc
的时候并不会增加对象的引用计数,只是在获取引用计数的时候会自动加1,那么Swift
是怎么做的呢?
首先我们先通过lldb
查看一下上述代码执行的结果。
首先我们从左侧直接复制t
对象的地址进行内存查看,然后在通过p/x
命令打印出t
对象的地址后,再次进行查看。
通过这两次查看,我们得到了两个不同的结果:
- 显然第二段内存中存储的不是一个地址
- 对于引用计数也不是很符合我们的预期
- 创建增加引用计数应该是3
- 不增加是2
-
p
命令貌似会增加对象的引用计数
1.2 refCounts源码分析
带着上面的结论(疑问),我们打开Swift
源码进行分析(这是一份Swift
5.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<InlineRefCountBits> InlineRefCounts
我们可以看到这是一个模板类,InlineRefCounts
是RefCounts
的别名,模板内部需要传入InlineRefCountBits
,下面我们分别来看看RefCounts
和InlineRefCountBits
。
RefCounts:
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
// 省略将近600行代码
}
这个类有将近600行代码,我们可以看到这里面的refCounts
是RefCountBits
类型,也就是传入的模板参数,所以说这里实际上是通过传入的模板参数进行处理一些事情。对于其他的方法都是模板类中定义的一些方法。由于代码过多,感兴趣的自己下载一份代码研究研究吧。下面我们看看InlineRefCounts
。
1.2.2 InlineRefCounts
返回后,点击InlineRefCounts
跳转到RefCount.h
文件中,可以看到如下代码:
typedef RefCountBitsT<RefCountIsInline> 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
是个enum
有true
和false
两个枚举值,所以在此处这个传入的值并不像InlineRefCounts
那样去做很多事情。所以我们就需要看看RefCountBitsT
这个模板类。
1.2.4 RefCountBitsT
RefCountBitsT
这个类也在RefCount.h
这个文件中,将近300行代码。感兴趣的自己下载全部源码进行查看吧,这里我们挑重点分析,这里面有个属性BitsType bits
,其他的都是针对这个属性的一些方法,所以我们认为这个属性很重要,那么BitsType
又是什么呢?这也是个别名,我们点击跳转,其实就在其上面隔两行代码处:
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::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 <RefCountInlinedness refcountIsInline, size_t sizeofPointer>
struct RefCountBitsInt;
// 64-bit inline
// 64-bit out of line
template <RefCountInlinedness refcountIsInline>
struct RefCountBitsInt<refcountIsInline, 8> {
typedef uint64_t Type;
typedef int64_t SignedType;
};
// 32-bit out of line
template <>
struct RefCountBitsInt<RefCountNotInline, 4> {
typedef uint64_t Type;
typedef int64_t SignedType;
};
// 32-bit inline
template <>
struct RefCountBitsInt<RefCountIsInline, 4> {
typedef uint32_t Type;
typedef int32_t SignedType;
}
看到这里就明朗了,对于这个bits
32位的就是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
。在这个类中我们开始查找它的初始化方法,首先我们看到的就是下面这个(还有其他三个),为什么选这个呢?因为参数里有strongExtraCount
和unownedCount
,跟引用计数可能相关。
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<sizeof(BitsType)>
Offsets
RefCountBitOffsets:
template <size_t sizeofPointer>
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位的整形结构中的存储会是这样的:
名称 | 含义 | 占位 |
---|---|---|
PureSwiftDealloc | 是否是纯Swift(或者调用Objc运行时) | 0 |
UnownedRefCount | 无主引用计数 | 1~31 |
IsDeiniting | 是否正在释放 | 32 |
StrongExtraRefCount | 强引用计数 | 33~62 |
UseSlowRC | 使用缓慢RC | 63 |
在Immortal
不朽(长生)的情况下,根据代码中的注释,这段是覆盖PureSwiftDealloc
和UnownedRefCount
,结合以往的源码,这段64位的整形结构中的存储会是这样的:
其实这里主要是将第一位的PureSwiftDealloc
替换成了IsImmortal
,根据注释,是这样的情况下,如果设置了所有位,这个对象是不能被释放的或者有一个refcount
的。
1.3.2 使用 SideTable 的情况
在后续的弱引用的情况下会使用到SideTable
,如果使用了SideTable
,这段64位的整形结构中的存储会是这样的:
名称 | 含义 | 占位 |
---|---|---|
SideTable | 是否正在释放 | 0~61 |
SideTableMark | 强引用计数 | 62 |
UseSlowRC | 使用缓慢RC | 63 |
1.4 结合lldb打印分析
按照上面的一系列分析,还有一开始我们的lldb打印的结果,我们来对照分析一下。首先打开计算器,将0x0000000400000003
这个16进制的值粘贴进去,结果如下:
我们可以看到此时的结果:
第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进制如下:
可以明显的看到,此时强引用计数是增加了1的。所以我们试着通过print
打印一下,看看什么结果。
我们分别在第一个断点前后查看了一下t
对象的内存段,发现并没有引用计数的变化。这是怎么回事呢?其实print
函数也会增加对象的引用计数,也就是调用swift_retain
,这点我们可以通过汇编代码验证:
在调用print
函数前,调用了一次swift_retain
,通过内存结构我们可以看出调用swift_retain
的对象正式t
,这里使用的是x86
汇编,这里的rax
寄存器中的地址与左侧的t
对象的内存地址是一样的。
那么为什么调用print
前后我们没有通过内存段看出强引用计数的增加呢?我觉得其内部应该是会进行release
的,虽然我没能够验证,但是我查了查网上的资料,有人也是这么说的,而且p
命令在lldb
调试阶段为了保证调试中不中断,所以增加了对象的引用计数也是可以理解的。暂且就这么认为吧。所以说:
- 通过
print
函数打印对象会调用对象retain
和release
- 通过
p
命令在lldb
中打印对象会增加对象的引用计数
1.5 无主引用 unowned
首先说一下什么是unowned
,在Swift
中有Strong
、weak
、unowned
三种引用方式,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
关键字的时候就会增加无主引用计数:
在断点前后我们通过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
在sil
代码中我们可以看到在对象拷贝的时候调用了copy_addr
命令,那么这个命令都做了什么呢?我们打开SIL参考文档,找到copy_addr
我们可以看到copy_addr
中会调用strong_retain
而strong_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方法分析:
- 首先根据
refCounts
取出原始的oldbits
- 然后判断增加的计数不为1,并且是不朽对象,则直接返回
- 通过一个
do while
循环处理引用计数加1的操作- 通过
incrementStrongExtraRefCount
函数对引用计数增加 - 返回值如果是快速的结果则为
true
,否则为false
- 为
false
的时候是设置了UseSlowRC
或者引用计数溢出了 - 如果不是快速的,判断不是不朽对象后则直接返回
- 如果是则调用
incrementSlow
函数进一步处理 - 循环的条件是新旧
bits
的内存结构是否一致在取反 - 如果上面修改成功了,则内存结构不一致,直接结束循环,如果一致则进入循环再次增加,这里应该是避免多线程操作时引用计数时取值不对的问题
- 通过
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);
}
- 该方法是实现强引用计数增加的
- 增加时是左移
StrongExtraRefCountShift
位(33位,从0开始计算) - 返回值是判断增加完的引用计数位是否大于等于0
- 是的话就是快速的
- 不是就是慢速的,慢速的可能需要借助散列表进行处理,也有可能设置了
UseSlowRC
incrementSlow:
template <typename RefCountBits>
void RefCounts<RefCountBits>::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();
}
}
- 该函数主要是三层判断
isImmortal(false)
直接返回 - 使用散列表则通过散列表进行增加强引用计数
- 其余的则
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
中对象的强引用在上面说了许多,但是乍一看也不是很好理解,总结一下吧。
- Swift同样使用
ARC
自动引用计数管理对象 - 对于强引用计数通过
refCounts
进行记录 -
refCounts
是由很多模板类定义的,在纯Swift
中主要做事的是RefCountBitsT
类 -
refCounts
实质是RefCountBitsT
中的bits
,也就是uint64_t
是一个64位的整形 - 通常情况下64位整形的第0位标记是否是纯
Swift
,否则就是调用objc
运行时处理 - 第1到31位是存储无主引用计数的
- 无主引用计数
unowned
是Swift
中弱引用的一种 -
unowned
通常用在确定不为空的防止循环引用的地方(例如init方法中的闭包里) -
Swift
对象初始化的是默认强引用计数为0,无主引用计数为1 - 当拷贝对象时会调用
swift_retain
方法增加对象的引用计数 - 使用
unowned
修饰对象是会使无主引用计数加1,实际是调用的swift_unownedRetain
方法增加引用计数的 -
swift
中对象的释放是在强引用和无主引用都为0的时候 - 如果类继承
NSObject
就会使用OC
的内存管理,感兴趣的可以看看OC
内存管理相关知识 - 对于弱引用和上面提到的散列表我们在下面的篇章会详细的说明
2. 弱引用
下面我们来说一下弱引用,在Swift
中弱引用分为两种,一种是我们熟知的weak
,另一种就是unowned
,关于unowned
在上一节强引用中已经做了详细的介绍,这里我们主要说一说weak
。
2.1 弱引用示例
还是举个例子:
var t = Teacher()
weak var t1 = t
我们可以看到在第一个断点前后分别查看t
对象的内存段会有明显的区别。这不仅仅是增加计数那么简单,那么这是为什么呢?
2.2 汇编代码分析
带着上一节的疑问,我们就看看汇编代码:
我们看到在将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<WeakReferenceBits> 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<InlineRefCountBits>::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<InlineRefCountBits>::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;
}
- 首先获取到原有的引用计数
oldbits
- 如果
oldbits
中已经存在SideTable
,则直接返回 - 如果正在销毁这个对象,则返回空指针
- 如果以上都没有,则直接初始化一个
new
一个新的SideTable
- 然后把新创建的
side
作为参数初始化InlineRefCountBits
,也就是在分析强引用是提到的其他三个初始化方法中的另一个(后面会接着分析) - 接下来是通过一个
do while
循环来处理,循环的条件是比较交换新旧bits
内存是否成功,在取反,在do
中:- 还是先判断了一下旧的
bits
中是否有sideTable
,如果有就删除刚刚初始化的,返回旧的 - 如果没有则再次判断是否正在销毁,是的话则返回空指针
- 将原有弱引用计数存储在
sidetable
中
- 还是先判断了一下旧的
- 返回
side
2.3.7 RefCountBitsT 的另一个初始化方法
接下来我们要先看看InlineRefCountBits(side)
涉及到的初始化方法,点击跳转过去:
LLVM_ATTRIBUTE_ALWAYS_INLINE
RefCountBitsT(HeapObjectSideTableEntry* side)
: bits((reinterpret_cast<BitsType>(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<HeapObject*> object;
SideTableRefCounts refCounts;
`
`
`
}
这也是个类,里面包含两个属性,一个是记录当前对象,另一个是refCounts
,类型是SideTableRefCounts
。
2.3.9 SideTableRefCounts & SideTableRefCountBits
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts
SideTableRefCounts
是取的别名,点击SideTableRefCountBits
跳转过去,在RefCount.h
文件中。
class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
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
打印的时候那个内存段,我们根据源码的思想来验证一下。重新运行了一下:
0xc0000000200c7e8a
,首先去掉最后两位,就变成了0x200C7E8A
,在按照初始化的时右移了三位,这回我们左移三位将其变回原来的值,即0x10063F450
。这个值我是用计算器算的。读取该地址的内存结构,结果如下图:
这这段内存实际是个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<RefCountNotInline>(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 <typename RefCountBits>
void RefCounts<RefCountBits>::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位的整形,只是在不同的地方而已,原来是HeapObject
的refCounts
现在是HeapObjectSideTableEntry
->SideTableRefCounts
->refCounts
。
对于再次使用无主引用的时候也是如此,这里就不过多介绍了。
对于原来有强引用,在上面介绍的时候已经说过,在生成sideTable
的时候,会存储旧的引用计数的。
2.6 小结
至此我们对swift
中的弱引用的分析基本就完毕了,在swift
中的弱引用对于HeapObject
来说,其refConuts
有两种:
- 无弱引用的时候:
strongCount
+unownedConut
- 有弱引用的时候:指向
HeapObjectSideTableEntry
的指针- 指针的获取需要去掉两个最高位
- 然后在左移三位
-
HeapObjectSideTableEntry
的存储结构如下:- HeapObject *object
- xxx(不知道是啥)
- strong Count + unowned Count(uint64_t)//64位
- 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<DontPerformDeinit>(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 <PerformDeinit performDeinit>
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<performDeinit>(oldbits, dec);
}
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return false; // don't deinit
}
这个方法跟上面几个方法写的思想一模一样。就不仔细分析了,这里面主要看一下decrementStrongExtraRefCount
和doDecrementSlow
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 <PerformDeinit performDeinit>
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<performDeinit>(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;
}
这个方法分几种情况来处理:
- 首先还是是否快速路径,是的话将
deinitNow
变量标记为false,这里也就是衰减正常完成,新的引用计数不为0 - 如果是不朽对象之间返回
false
- 如果是使用
sideTable
,则调用doDecrementNonAtomicSideTable
进行返回 - 到了这里就是
deinit
了- 标记
deinitNow
为true
- 将强引用计数置为0
- 标记当为正在析构的状态
- 标记
- 这里会将
newbits
存储到refCounts
,(包括正常衰减不为0和deinit
的时候) - 接下来判断
performDeinit && deinitNow
调用_swift_release_dealloc
方法 - 最后返回
deinitNow
3.6 散列表release
template <>
template <PerformDeinit performDeinit>
inline bool RefCounts<InlineRefCountBits>::
doDecrementNonAtomicSideTable(InlineRefCountBits oldbits, uint32_t dec) {
auto side = oldbits.getSideTable();
return side->decrementNonAtomicStrong<performDeinit>(dec);
}
template <PerformDeinit performDeinit>
bool decrementNonAtomicStrong(uint32_t dec) {
return refCounts.doDecrementNonAtomic<performDeinit>(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 <PerformDeinit performDeinit>
inline bool RefCounts<SideTableRefCountBits>::
doDecrementNonAtomic(uint32_t dec) {
return doDecrement<performDeinit>(dec);
}
经过几次调用,我们发现再次调用回了doDecrement
方法。这是处理sideTable
里面的强引用。
3.7 小结
关于swift
的release
基本就分析这么多了做个总结:
- 这里首先会当做正常的纯
swift
去做引用计数的衰减,如果衰减为0以下,则进行其他处理,否则就是正常衰减 - 对于正常衰减的会重置
newBits
- 如果减没了就要
deinit
-
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 <typename Runtime>
struct TargetHeapMetadataHeaderPrefix {
/// Destroy the object, returning the allocated size of the object
/// or 0 if the object shouldn't be deallocated.
TargetSignedPointer<Runtime, HeapObjectDestroyer *__ptrauth_swift_heap_object_destructor> 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<Runtime, ClassIVarDestroyer * __ptrauth_swift_heap_object_destructor> IVarDestroyer;
/// The heap-destructor function.
TargetRelativeDirectPointer<Runtime, HeapObjectDestroyer> Destroy;
/// The ivar-destructor function.
TargetRelativeDirectPointer<Runtime, ClassIVarDestroyer, /*nullable*/ true>
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<PointerTy>(absolute);
}
到了这里我感觉是通过指针偏移去找到一个方法,然后通过指针直接调用这个方法。那么究竟调用了那个方法呢?我们这里通过汇编代码看一看
4.2 通过汇编分析
编写如下代码:
class Teacher {}
do {
var t = Teacher()
}
在作用域结束后就会release
,我们添加swift_release_dealloc
符号断点,运行。
在call
处添加断点,过掉上个断点就会来到call
处,点击step into
:
此时我们可以看到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);
}
我们可以看到,对于Swift
和OC
交互的时候进行额外的处理。这里主要是需要让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<const ClassMetadata*>(object->metadata)->isTypeMetadata());
if (object->refCounts.decrementUnownedShouldFree(1)) {
auto classMetadata = static_cast<const ClassMetadata*>(object->metadata);
swift_slowDealloc(object, classMetadata->getInstanceSize(),
classMetadata->getInstanceAlignMask());
}
}
在无主引用计数release
完成后同样会调用swift_slowDealloc
方法进行内存的释放。
关于无主引用计数的release
其思想与强引用类似,会调用decrementUnownedShouldFree
方法,感兴趣的可以自己去跟一跟源码,这里就不在过多的粘贴源码了。
4.4 小结
关于swift
的dealloc
基本就分析完毕了,现在总结如下:
-
swift
的dealloc
首先会调用_swift_release_dealloc
方法 - 关于
destroy
上面做了些分析,最后我觉得是通过内存偏移找到的其他方法,最终调用到swift_deallocClassInstance
- 析构的时候主要是释放内存
- 如果还存在无主引用,就会优先处理无主引用
- 处理完毕就会释放内存
- 所以
swift
的析构并不是强引用衰减完毕,还要处理无主引用