我正在参与「启航计划」
首要学习强引证、弱引证、循环引证的解决,重点在于强引证和弱引证的底层结构
首要内容:
- 强引证
- 弱引证
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
代码:
structHeapObject{
HeapMetadataconst*metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
...
}
#defineSWIFT_HEAPOBJECT_NON_OBJC_MEMBERS\
InlineRefCountsrefCounts
阐明:
- 目标在底层是HeapObject
- 所以在源码中检查HeapObject,发现引证是SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS
- 经过SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS的界说能够看到是refCounts的类型是InlineRefCounts
1.2.2. InlineRefCounts
代码:
typedefRefCounts<InlineRefCountBits>InlineRefCounts;
template<typenameRefCountBits>
classRefCounts{
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<RefCountInlinednessrefcountIsInline>
classRefCountBitsT{
...
typedeftypenameRefCountBitsInt<refcountIsInline,sizeof(void*)>::Type
BitsType;
...
BitsTypebits;
...
}
template<>
structRefCountBitsInt<RefCountNotInline,4>{
//类型
typedefuint64_tType;
typedefint64_tSignedType;
};
阐明:
- 能够看到有一个bits特点,这个特点便是Bits
- bits其实质是将RefCountBitsInt中的type特点取了一个别号
- 所以bits的真实类型是uint64_t即64位整型的数组,包含Type和SignedType两个成员
能够看出refCounts中其实包含有Bits成员,而这个成员是64位整型的数组。
1.3 refCounts的赋值探究
经过探究swift_allocObject,来检查引证特点的的赋值进程
1.3.1. swift_allocObject
代码:
staticHeapObject*_swift_allocObject_(HeapMetadataconst*metadata,
size_trequiredSize,
size_trequiredAlignmentMask){
...
new(object)HeapObject(metadata);
...
}
<!--结构函数-->
constexprHeapObject(HeapMetadataconst*newMetadata)
:metadata(newMetadata)
,refCounts(InlineRefCounts::Initialized)
{}
阐明:
- 经过swift_allocObject办法来结构目标,因而在这儿会进行目标中引证特点的赋值
- 能够看到是经过refCounts来给引证计数赋值的,传入的参数是Initialized
1.3.2. Initialized
代码:
enumInitialized_t{Initialized};
//对应的RefCounts办法
//Refcountofanewobjectis1.
constexprRefCounts(Initialized_t)
:refCounts(RefCountBits(0,1)){}
阐明:
- 进入Initialized界说,是一个枚举
- 其对应的refCounts办法中,看出真实干事的是RefCountBits
1.3.3. RefCountBits
代码:
template<typenameRefCountBits>
classRefCounts{
std::atomic<RefCountBits>refCounts;
...
}
阐明:
- 进入RefCountBits界说,也是一个模板界说
1.3.4. RefCountBitsT
代码:
LLVM_ATTRIBUTE_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_tstrongExtraCount,uint32_tunownedCount)
: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_
staticHeapObject*_swift_retain_(HeapObject*object){
SWIFT_RT_TRACK_INVOCATION(object,swift_retain);
if(isValidPointerForNativeRetain(object))
object->refCounts.increment(1);
returnobject;
}
voidincrement(uint32_tinc=1){
autooldbits=refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
//constantpropagationwillremovethisinswift_retain,itshouldonly
//bepresentinswift_retain_n
if(inc!=1&&oldbits.isImmortal(true)){
return;
}
//64位bits
RefCountBitsnewbits;
do{
newbits=oldbits;
boolfast=newbits.incrementStrongExtraRefCount(inc);
if(SWIFT_UNLIKELY(!fast)){
if(oldbits.isImmortal(false))
return;
returnincrementSlow(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_NODISCARDLLVM_ATTRIBUTE_ALWAYS_INLINE
boolincrementStrongExtraRefCount(uint32_tinc){
//ThisdeliberatelyoverflowsintotheUseSlowRCfield.
//对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);
returnref;
}
阐明:
- 检查swift_weakInit,能够看到系统本身提供了一个WeakReference类,用来进行引证计数的创立
- 这个ref目标调用nativeInit,传入目标,就能够创立弱引证了
2.3.2. nativeInit
代码:
voidnativeInit(HeapObject*object){
autoside=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
autoside=allocateSideTable(true);
if(side)
//假如创立成功,则添加弱引证
returnside->incrementWeak();
else
returnnullptr;
}
阐明:
- 创立散列表
- 经过incrementWeak()给散列表添加弱引证
2.3.4. allocateSideTable
代码:
template<>
HeapObjectSideTableEntry*RefCounts<InlineRefCountBits>::allocateSideTable(boolfailIfDeiniting)
{
//1、先拿到原本的引证计数
autooldbits=refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
//Preflightfailuresbeforeallocatinganewsidetable.
if(oldbits.hasSideTable()){
//Alreadyhaveasidetable.Returnit.
returnoldbits.getSideTable();
}
elseif(failIfDeiniting&&oldbits.getIsDeiniting()){
//Alreadypastthestartofdeinit.Donothing.
returnnullptr;
}
//Preflightpassed.Allocateasidetable.
//FIXME:customsidetableallocator
//2、创立sideTable
HeapObjectSideTableEntry*side=newHeapObjectSideTableEntry(getHeapObject());
//3、将创立的地址给到InlineRefCountBits
autonewbits=InlineRefCountBits(side);
do{
if(oldbits.hasSideTable()){
//Alreadyhaveasidetable.Returnitanddeleteours.
//Readbeforedeletetostreamlinebarriers.
autoresult=oldbits.getSideTable();
deleteside;
returnresult;
}
elseif(failIfDeiniting&&oldbits.getIsDeiniting()){
//Alreadypastthestartofdeinit.Donothing.
returnnullptr;
}
side->initRefCounts(oldbits);
}while(!refCounts.compare_exchange_weak(oldbits,newbits,
std::memory_order_release,
std::memory_order_relaxed));
returnside;
}
阐明:
- 经过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,所以依然会包含强引证和无主引证。
- 没有弱引证