我正在参加「掘金·启航计划」
主要学习强引用、弱引用、循环引用的解决,重点在于强引用和弱引用的底层结构
主要内容:
- 强引用
- 弱引用
1. 强引用
通过一个对象的创建,在源码中查看引用计数
1.1 创建
代码:
class WYTeacher {
var age: Int = 18
var name: String = " WY"
}
var t = WYTeacher()
var t1 = t
var t2 = t
查看:
说明:
- 直接将对象赋值给一个变量,就是强引用
- 可以查看此时的refCounts的值
1.2 refCounts的认识
通过在底层源码中查找HeapObject来查看对象引用的计数。 引用计数refCounts属性其本质是RefCountsInt类型,包含unit32_t Type和int32_t SignedType
1.2.1. HeapObject
代码:
struct HeapObject {
HeapMetadata const *metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
...
}
👇
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
说明:
- 对象在底层是HeapObject
- 所以在源码中查看HeapObject,发现引用是SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS
- 通过SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS的定义可以看到是refCounts的类型是InlineRefCounts
1.2.2. InlineRefCounts
代码:
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
👇
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
}
说明:
- 查看InlineRefCounts,发现它是RefCounts的别名
- 而RefCounts的成员是refCounts,它是由RefCountBits决定的
- 因此是InlineRefCounts通过InlineRefCountBits来决定里面的值
1.2.3. InlineRefCountBits
代码:
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
说明:
- 继续查找InlineRefCountBits;,发现它是RefCountBitsT的别名
1.2.4. RefCountBitsT
代码:
template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {
...
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
BitsType;
...
BitsType bits;
...
}
👇
template <>
struct RefCountBitsInt<RefCountNotInline, 4> {
//类型
typedef uint64_t Type;
typedef int64_t SignedType;
};
说明:
- 可以看到有一个bits属性,这个属性就是Bits
- bits其实质是将RefCountBitsInt中的type属性取了一个别名
- 所以bits的真正类型是uint64_t即64位整型的数组,包含Type和SignedType两个成员
可以看出refCounts中其实包含有Bits成员,而这个成员是64位整型的数组。
1.3 refCounts的赋值探索
通过探索swift_allocObject,来查看引用属性的的赋值过程
1.3.1. swift_allocObject
代码:
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
...
new (object) HeapObject(metadata);
...
}
👇
<!--构造函数-->
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
说明:
- 通过swift_allocObject方法来构造对象,因此在这里会进行对象中引用属性的赋值
- 可以看到是通过refCounts来给引用计数赋值的,传入的参数是Initialized
1.3.2. Initialized
代码:
enum Initialized_t { Initialized };
//对应的RefCounts方法
// Refcount of a new object is 1.
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}
说明:
- 进入Initialized定义,是一个枚举
- 其对应的refCounts方法中,看出真正干事的是RefCountBits
1.3.3. RefCountBits
代码:
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
}
说明:
- 进入RefCountBits定义,也是一个模板定义
1.3.4. RefCountBitsT
代码:
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))
{ }
说明:
- 这里就是真正的初始化地方
- 实际上是做了一个位域操作
1.4 RefCountsBit结构
经过上文的查找,可以得到RefCountsBit结构
说明:
- isImmortal(0)
- UnownedRefCount(1-31):unowned的引用计数
- isDeinitingMask(32):是否进行释放操作
- StrongExtraRefCount(33-62):强引用计数
- UseSlowRC(63)
1.5 分析SIL代码
1.5.1. 创建对象,第一次变量赋值
代码:
class WYTeacher {
var age: Int = 18
var name: String = " WY"
}
var t = WYTeacher()
var t1 = t
SIL文件:
说明:
- 先创建一个全局的实例变量
- 之后创建对象
- 将对象赋值给全局变量
1.5.2. 第二次变量赋值
SIL文件:
说明:
- 先创建t1的变量
- 之后通过%3拿到对象
- 通过copy_addr将拿到的对象赋值给t1
- %new = load $*LGTeacher(拿到这个对象)
- strong_retain %new(给这个对象引用计数加一)
- store %new to %9(将这个对象赋给t1)
- copy_addr最主要的就是对这个对象做了一下strong_ratain。之后再进行赋值操作
1.5.3. swift_retain的认识
strong_retain对应的就是 swift_retain,其内部是一个宏定义,内部是_swift_retain_,其实现是对object的引用计数作+1操作。
SIL文件:
//内部是一个宏定义
HeapObject *swift::swift_retain(HeapObject *object) {
CALL_IMPL(swift_retain, (object));
}
👇
//本质调用的就是 _swift_retain_
static HeapObject *_swift_retain_(HeapObject *object) {
SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
if (isValidPointerForNativeRetain(object))
object->refCounts.increment(1);
return object;
}
👇
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;
}
//64位bits
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));
}
说明:
- 通过refCounts.increment(1);进行引用计数+1
- 在increment方法中是通过incrementStrongExtraRefCount来实现计数的
1.5.4. incrementStrongExtraRefCount
SIL文件:
LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
bool incrementStrongExtraRefCount(uint32_t inc) {
// This deliberately overflows into the UseSlowRC field.
// 对inc做强制类型转换为 BitsType
// 其中 BitsType(inc) << Offsets::StrongExtraRefCountShift 等价于 1<<33位,16进制为 0x200000000
//这里的 bits += 0x200000000,将对应的33-63转换为10进制,为
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}
说明:
- 通过位域进行偏移
- 我们知道refCounts中的62-33位是strongCount,这里的位域目的就是将引用计数加在这里
- 每次的增加为1左移33位
- 也就是每次增加0x200000000
- 也就是BitsType(inc) << Offsets::StrongExtraRefCountShift
1.6 引用计数的计算
因为refCounts中的62-33位是strongCount,所以每次引用计数+1,都要加上0x200000000。BitsType(inc) << Offsets::StrongExtraRefCountShift就代表1<<33位。
计算:
- 只有t时的refCounts是 0x0000000200000003
- t + t1时的refCounts是 0x0000000400000003 = 0x0000000200000003 + 0x200000000
- t + t1 + t2 时的refCounts是 0x0000000600000003 = 0x0000000400000003 + 0x200000000
运行查看:
说明:
- 可以看到swift中创建实例对象时默认为1
- 可以通过CFGetRetainCount查到一个对象的引用计数
1.7 总结
- swift中创建实例对象时默认为1
- 强引用计数存储在refCounts中的62-33位,因此每次引用计数+1,都要加上0x200000000
2. 弱引用
主要认识弱引用的使用、以及散列表的存储结构。散列表不仅存储了弱引用,还有强引用和无主引用,如果有弱引用,在refCounts中直接使用散列表的地址
2.1 弱引用的使用
代码:
class WYTeacher {
var age: Int = 18
var name: String = "WY"
var stu: WYStudent?
}
class WYStudent {
var age = 20
var teacher: WYTeacher?
}
func test(){
var t = WYTeacher()
weak var t1 = t
}
说明:
- 给变量t赋值给弱引用t1
- 弱引用声明的变量是一个可选值,因为在程序运行过程中是允许将当前变量设置为nil的
2.2 debug查看底层方法
查看引用计数变化:
在t1处加断点,查看汇编:
说明:
- 可以看到在底层是使用swift_weakInit来设置弱引用的
- refCounts处存储的是一个地址
2.3 底层源码探索
2.3.1. swift_weakInit
代码:
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
说明:
- 查看swift_weakInit,可以看到系统本身提供了一个WeakReference类,用来进行引用计数的创建
- 这个ref对象调用nativeInit,传入对象,就可以创建弱引用了
2.3.2. nativeInit
代码:
void nativeInit(HeapObject *object) {
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
说明:
- 在方法中,通过对象的refCounts属性的formWeakReference()方法来具体实现
- 这个在对象底层结构中可以知道有这个方法
2.3.3. formWeakReference
代码:
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
//创建 sideTable
auto side = allocateSideTable(true);
if (side)
// 如果创建成功,则增加弱引用
return side->incrementWeak();
else
return nullptr;
}
说明:
- 创建散列表
- 通过incrementWeak()给散列表增加弱引用
2.3.4. allocateSideTable
代码:
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
// 1、先拿到原本的引用计数
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
//2、创建sideTable
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
// 3、将创建的地址给到InlineRefCountBits
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;
}
说明:
- 通过allocateSideTable构建散列表
-
- 先拿到原本的引用计数
- 散列表也需要存储强引用的引用计数
- 如果这个bits本来就有散列表了,就直接使用原有的散列表
-
- 创建散列表
- 这个散列表只是针对当前对象的
-
- 通过InlineRefCountBits将散列表的地址赋给值newBits
- 因此如果有弱引用,那么引用计数中存储的就是散列表地址
2.3.5. 查看弱引用的RefCountBitsT的创建过程
代码:
说明:
- 传入的参数是HeapObjectSideTableEntry的方法,就是用来构建散列表的Bits
- 将散列表地址通过偏移操作存储到RefCountBits内存中
- 在这里可以看到,除去63、62位,剩下的地方用来存储了散列表地址
2.3.6. HeapObjectSideTableEntry
代码:
说明:
- 查看散列表的结构
- 包括object和refCounts
- 其实可以看做分别将对象和引用计数作为键和值
- 这里的引用计数表就是散列表
2.3.7. SideTableRefCountBits
代码:
说明:
- 继承自RefCountBitsT
- 只有一个属性weakBits,是弱引用属性
- 在RefCountBitsT中还有一个属性,64位用来记录原有的强引用计数。
2.4 验证
代码:
计算过程:
说明:
- 散列表地址 = refCounts 清除63、62位 + 左移三位
- 上面我们拿到了弱引用后的地址0xc000000020261de6
- 将62、63清零,变成0x20261DE6
- 然后左移3位变成0x10130EF30,这个就是散列表地址
- 查看散列表内容
- 包含两个内容,一个是对象,所以会打印对象的地址
- 一个是引用表,引用表包含强引用计数和弱引用计数
2.5 总结
- 当给一个对象增加弱引用后,会创建一个散列表用来存储弱引用和强引用
- 并且将散列表的地址存储到bits中
- 因此对于HeapObject有两种refCounts方式
- 没有弱引用
- 没有弱引用,只有强引用和无主引用
- strongCount + unonwnedCount
- 有弱引用
- 如果有弱引用,那么存储的是一个散列表地址
- 而散列表中存储的是object和散列表信息
- 散列表信息又包含了weakBits
- 同时又因为继承RefCountBitsT,所以依然会包含强引用和无主引用。
- 没有弱引用