小言_互联网的博客

6、Swift内存管理

359人阅读  评论(0)

Swift中内存管理使⽤⾃动引⽤计数(ARC)机制来追踪和管理内存。

下面我们来看强引用案例


  
  1. class HSTeacher{
  2.     var age: Int = 29
  3.     var name: String  = "Hellon"
  4. }
  5. var t = HSTeacher()
  6. var t1 = t
  7. var t2 = t
  8. print( "end") //断点
  • 控制台在断点时、输出t变量的地址、然后格式化输出地址得

  
  1. (lldb) po t
  2. < LGTeacher: 0x1006523d0>
  3. (lldb) x/8g 0x1006523d0
  4. 0x1006523d0: 0x000000010000c420 0x0000000600000002
  5. 0x1006523e0: 0x000000000000001d 0x00006e6f6c6c6548
  6. 0x1006523f0: 0xe600000000000000 0x00000001006524f0
  7. 0x100652400: 0x0000000000000000 0x0000000000000000
  • 经过前面Swift创建对象的探索、我们得到refCounts 为 0x0000000600000002、然而这和我们所理解的引用计数个数不太一致、

下面我们进入源码分析

  • 直接定位到HeapObject.cpp中、点击HeapObject对象、我们可以看到其结构体结构为

  
  1. struct HeapObject {
  2. /// This is always a valid pointer to a metadata object.
  3. HeapMetadata const *metadata;
  4. SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
  5. ....
  6. };
  • 下面我们进入到其中的宏定义 SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS 中

  
  1. // The members of the HeapObject header that are not shared by a
  2. // standard Objective-C instance
  3. #define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
  4. InlineRefCounts refCounts
  •     这就是我们的引用计数 refCounts了
  • 继续开始探索、定义refCounts的这个类 InlineRefCounts

  
  1. typedef RefCounts< InlineRefCountBits> InlineRefCounts;
  2. typedef RefCounts< SideTableRefCountBits> SideTableRefCounts;
  • 于是乎、InlineRefCounts 被起了个别名为 RefCounts、下面开始看 RefCounts类结构

  
  1. template <typename RefCountBits> //模版类
  2. class RefCounts {
  3. std::atomic< RefCountBits> refCounts;
  4. ......
  5. public:
  6. enum Initialized_t { Initialized };
  7. enum Immortal_t { Immortal };
  8. RefCounts() = default;
  9. // Refcount of a new object is 1.
  10. constexpr RefCounts( Initialized_t)
  11. : refCounts( RefCountBits( 0, 1)) {}
  12. // Refcount of an immortal object has top and bottom bits set
  13. constexpr RefCounts( Immortal_t)
  14. : refCounts( RefCountBits( RefCountBits:: Immortal)) {}
  15. .......
  16. }
  • 由RefCounts类的上边、我们发现它是个模版类、那么真正起作用的是传进来的模版参数、
typedef RefCounts<InlineRefCountBits> InlineRefCounts
  • 由上述类型别名定义可得模版参数为 InlineRefCountBits、探索该类的结构如下
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
  • 万万没想到、又是一个模版类、那么我们直接就看 RefCountIsInline是个什么东西?

  
  1. // RefCountIsInline: refcount stored in an object
  2. // RefCountNotInline: refcount stored in an object's side table entry
  3. enum RefCountInlinedness { RefCountNotInline = false, RefCountIsInline = true };
  • 终于看到了个正常的玩意、RefCountIsInline表示对象中的refCount。RefCountInlinedness 是个枚举、要么false要么true
  • 回过头来、我们继续看RefCountBitsT 是个什么东西呢?

  
  1. // Basic encoding of refcount and flag data into the object's header.
  2. template < RefCountInlinedness refcountIsInline>
  3. class RefCountBitsT {
  4. friend class RefCountBitsT<RefCountIsInline>;
  5. friend class RefCountBitsT<RefCountNotInline>;
  6. static const RefCountInlinedness Inlinedness = refcountIsInline;
  7. typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
  8. BitsType;
  9. typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::SignedType
  10. SignedBitsType;
  11. typedef RefCountBitOffsets<sizeof(BitsType)>
  12. Offsets;
  13. BitsType bits;
  14. .....
  15. }
  • 对上述类的分析、我们需要看其成员变量 BitsType bits; 而它本身又是一个类内部的宏定义取别名

  
  1. typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>:: Type
  2. BitsType;
  • 其中的RefCountBitsInt结构为如下

  
  1. template < RefCountInlinedness refcountIsInline>
  2. struct RefCountBitsInt<refcountIsInline, 8> {
  3. typedef uint64_t Type;
  4. typedef int64_t SignedType;
  5. };
  • 那么本质上 BitsType就是 将RefCountBitsInt内部的uint64_t Type取的别名
  • 下面我们回到 HeapObject 结构体中

  
  1. struct HeapObject {
  2. /// This is always a valid pointer to a metadata object.
  3. HeapMetadata const *metadata;
  4. SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
  5. #ifndef __swift__
  6. HeapObject() = default;
  7. //根据新分配的对象初始化HeapObject
  8. constexpr HeapObject( HeapMetadata const *newMetadata)
  9. : metadata(newMetadata)
  10. , refCounts( InlineRefCounts:: Initialized)
  11. { }
  12. .....
  13. };
  • 初始化方法中、我们传入了 newMetadata、Initialized、其中的Initialized 、我们可以发现其实它是一个enum
enum Initialized_t { Initialized };
  • 并且其中的 Initialized_t 使用在引用计数上、并且表明新建的对象其Refcount为 1

  
  1. constexpr RefCounts( Initialized_t)
  2. : refCounts( RefCountBits( 0, 1)) {}
  • 其中使用的RefCountBits(0,1)、让我们回到了 RefCountBitsT模版定义类上、所以我们着重分析该类的结构
  • 首先查找该类的初始化方法、根据其中传入的(0,1)参数定位到如下初始化方法

  
  1. LLVM_ATTRIBUTE_ALWAYS_INLINE
  2. constexpr
  3. RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
  4. : bits(( BitsType(strongExtraCount) << Offsets:: StrongExtraRefCountShift) |
  5. ( BitsType(unownedCount) << Offsets:: UnownedRefCountShift))
  6. { }
  • 由该初始化方法、我们看出:对传入的0、1参数进行类按位偏移的操作、Offsets结构为

  
  1. typedef RefCountBitOffsets< sizeof( BitsType)>
  2. Offsets;
  • 由此我们看出、Offsets的偏移量取决于成员变量BitsType属于哪种类型

  
  1. template <>
  2. struct RefCountBitOffsets<8> {
  3. static const size_t IsImmortalShift = 0;
  4. static const size_t IsImmortalBitCount = 1;
  5. static const uint64_t IsImmortalMask = maskForField( IsImmortal);
  6. static const size_t UnownedRefCountShift = shiftAfterField( IsImmortal);
  7. static const size_t UnownedRefCountBitCount = 31;
  8. static const uint64_t UnownedRefCountMask = maskForField( UnownedRefCount);
  9. static const size_t IsDeinitingShift = shiftAfterField( UnownedRefCount);
  10. static const size_t IsDeinitingBitCount = 1;
  11. static const uint64_t IsDeinitingMask = maskForField( IsDeiniting);
  12. static const size_t StrongExtraRefCountShift = shiftAfterField( IsDeiniting);
  13. static const size_t StrongExtraRefCountBitCount = 30;
  14. static const uint64_t StrongExtraRefCountMask = maskForField( StrongExtraRefCount);
  15. static const size_t UseSlowRCShift = shiftAfterField( StrongExtraRefCount);
  16. static const size_t UseSlowRCBitCount = 1;
  17. static const uint64_t UseSlowRCMask = maskForField( UseSlowRC);
  18. static const size_t SideTableShift = 0;
  19. static const size_t SideTableBitCount = 62;
  20. static const uint64_t SideTableMask = maskForField( SideTable);
  21. static const size_t SideTableUnusedLowBits = 3;
  22. static const size_t SideTableMarkShift = SideTableBitCount;
  23. static const size_t SideTableMarkBitCount = 1;
  24. static const uint64_t SideTableMarkMask = maskForField( SideTableMark);
  25. };
  • 由源码指示得到强引用计数时

  • IsImmortal 从第0位开始、占用1个字节

  • UnownedRefCount 从第1位开始、占用31字节
  • IsDeiniting 从第32位开始、占用1字节
  • StrongExtraRefCount 从第33位开始、占用30字节
  • UseSlowRC 从第63位开始、占用1字节

因此我们汇总得到


  
  1. 位数 Type
  2. 0 IsImmortal
  3. 1- 31 UnownedRefCount
  4. 32 IsDeiniting
  5. 33- 62 StrongExtraRefCount
  6. 63 UseSlowRC
  • 弱引用计数时
    • SideTable 从第0位开始、占用62字节
    • SideTableMark 从62位开始、占用1字节
    • UseSlowRC 从第63位开始、占用1字节
    • SideTableUnusedLowBits 未使用的有3位低位、用作了偏移

大功告成、

  • 我们现在再来看refCounts 0x0000000600000002表示为几?
  • 直接看看不出来、先拿计算器!!!!计算器--显示 -编程型、复制引用计数、粘贴到计算器上、

  • 第1位为无主引用计数为1、第33位开始为强引用计数、33到62之间为 11、那么强引用计数为3咯;
  • 因此我们在看到这样类型的refCounts时、要自然而然的有种亲切感、6的二进制为 110、右移一位开始才算强引用计数哦、剃掉那个0、不就是3咯。
  • 回到最初的地方、我们通过SIL底层分析一下HSTeacher的实例变量赋值操作到底干了啥?才导致引用计数每赋值一次就+1、
  • 查看SIL文件中的 @main 函数 内部

  
  1. // main
  2. sil @main : $ @convention( c) ( Int32, UnsafeMutablePointer< Optional< UnsafeMutablePointer< Int8>>>) -> Int32 {
  3. bb0(% 0 : $ Int32, % 1 : $ UnsafeMutablePointer< Optional< UnsafeMutablePointer< Int8>>>):
  4. alloc_global @$s4main1tAA9HSTeacherCvp // id: %2
  5. % 3 = global_addr @$s4main1tAA9HSTeacherCvp : $* HSTeacher // users: %7, %15, %10
  6. % 4 = metatype $@thick HSTeacher. Type // user: %6
  7. // function_ref HSTeacher.__allocating_init()
  8. % 5 = function_ref @$s4main9HSTeacherCACycfC : $ @convention(method) (@thick HSTeacher. Type) -> @owned HSTeacher // user: %6
  9. % 6 = apply % 5(% 4) : $ @convention(method) (@thick HSTeacher. Type) -> @owned HSTeacher // user: %7
  10. store % 6 to % 3 : $* HSTeacher // id: %7
  11. alloc_global @$s4main2t1AA9HSTeacherCvp // id: %8
  12. //拿到全局变量地址
  13. % 9 = global_addr @$s4main2t1AA9HSTeacherCvp : $* HSTeacher // user: %11
  14. % 10 = begin_access [read] [ dynamic] % 3 : $* HSTeacher // users: %12, %11
  15. //先去加载当前的值、 %new = load $*HSTeacher
  16. //编译器会默认 strong_retain %new
  17. //把我们当前变量中的内容赋值到 %9
  18. copy_addr % 10 to [initialization] % 9 : $* HSTeacher // id: %11
  19. end_access % 10 : $* HSTeacher // id: %12
  20. alloc_global @$s4main2t2AA9LGTeacherCvp // id: %13
  21. .......
  22. }
  • 没错、关键点在 copy_addr关键字、就是这个导致每赋值一次就调用一次strong_retain、本质上其实调用了 swift_retain函数。
  • 下面、接下来、最重要的是:再回到源码、我们搜索swift_retain具体实现、来到HeapObject.cpp
  • 一些专有的内部调用

  
  1. HeapObject *swift::swift_retain( HeapObject *object) {
  2. CALL_IMPL(swift_retain, (object));
  3. }
  4. HeapObject *(*swift::_swift_retain)( HeapObject *object) = _swift_retain_;
  • 最后我们得到这样 swift_retain ---> _swift_retain_ 、找它!!!!

  
  1. static HeapObject *_swift_retain_( HeapObject *object) {
  2. SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
  3. if (isValidPointerForNativeRetain(object))
  4. object->refCounts.increment( 1);
  5. return object;
  6. }
  • 惊喜在这咯、refCounts.increment(1)操作、

  
  1. // Increment the reference count.
  2. void increment(uint32_t inc = 1) {
  3. auto oldbits = refCounts.load( SWIFT_MEMORY_ORDER_CONSUME);
  4. RefCountBits newbits;
  5. do {
  6. newbits = oldbits;
  7. bool fast = newbits.incrementStrongExtraRefCount(inc);
  8. if ( SWIFT_UNLIKELY(!fast)) {
  9. if (oldbits.isImmortal())
  10. return;
  11. return incrementSlow(oldbits, inc);
  12. }
  13. } while (!refCounts.compare_exchange_weak(oldbits, newbits,
  14. std::memory_order_relaxed));
  15. }
  • 我们再看 incrementStrongExtraRefCount 对这个 inc 做了什么?inc = 1

  
  1. // Returns true if the increment is a fast-path result.
  2. // Returns false if the increment should fall back to some slow path
  3. // (for example, because UseSlowRC is set or because the refcount overflowed).
  4. LLVM_NODISCARD LLVM_ATTRIBUTE_ALWAYS_INLINE
  5. bool incrementStrongExtraRefCount(uint32_t inc) {
  6. // This deliberately overflows into the UseSlowRC field.
  7. bits += BitsType(inc) << Offsets:: StrongExtraRefCountShift;
  8. return ( SignedBitsType(bits) >= 0);
  9. }
  • BitsTyp 对 inc 做了强制类型转换、转为uint64_t类型、接下来将其偏移存放到 StrongExtraRefCount内存地址上、
  • bits作为RefCountBitsT类中的成员变量、一直在做 + 操作。
  • 所以每一次的swift_retain操作、都是在增加引用计数、也就是类的实例对象每一次的赋值操作都是在增加引用计数。

下面分析弱引用计数


  
  1. class HSTeacher {
  2.     var age: Int = 19
  3.     var name: String = "Holo"
  4.     deinit {
  5.         print( "HSTeacher deinit")
  6.     }
  7. }
  8. var t = HSTeacher()
  9. weak var t1 = t //断点1
  10. print( "end") //断点2
  • 当我们使用weak修饰变量时、并不会对当前强引用产生影响、
  • 查看 t 引用计数、发现其引用计数位发生改变

  
  1. (lldb) v t
  2. ( SwiftExecite. HSTeacher) t = 0x0000000100520480 (age = 19, name = "Holo")
  3. (lldb) x/4g 0x0000000100520480
  4. 0x100520480: 0x0000000100008210 0x0000000000000002
  5. 0x100520490: 0x0000000000000013 0x000000006f6c6f48
  6. (lldb) x/4g 0x0000000100520480
  7. 0x100520480: 0x0000000100008210 0xc0000000200c6b78
  8. 0x100520490: 0x0000000000000013 0x000000006f6c6f48
  • 然而当前获取t变量存放的强引用计数、发现其本质上并没有发生改变

  
  1. (lldb) po CFGetRetainCount(t)
  2. 2
  • 顾我们来看weak修饰后的变量t1、编译器提示为可选类型、

  • t = nil 时 'nil' cannot be assigned to type 'HSTeacher'
  • t1 = nil时、则编译器不会报错。
  • 表明当前弱引用并不会对当前t实例对象保持强引用、顾不会阻止强引用计数为0时释放该对象
  • 运行代码、在断点1处我们查看到汇编

  
  1.   0x10000228a <+ 106>: callq  0x100003ba8               ; symbol stub for: swift_retain
  2.     0x10000228f <+ 111>: leaq   - 0x20(%rbp), %rdi
  3.     0x100002293 <+ 115>: movq   %rax, - 0x50(%rbp)
  4.     0x100002297 <+ 119>: callq  0x100003b9c               ; symbol stub for: swift_endAccess
  5.     0x10000229c <+ 124>: leaq   0x60cd(%rip), %rdi        ; SwiftExecite.t1 : Swift. Optional< SwiftExecite. HSTeacher>
  6.     0x1000022a3 <+ 131>: movq   - 0x48(%rbp), %rsi
  7.     0x1000022a7 <+ 135>: callq  0x100003bae               ; symbol stub for: swift_weakInit
  8.     0x1000022ac <+ 140>: movq   - 0x48(%rbp), %rdi
  9.     0x1000022b0 <+ 144>: movq   %rax, - 0x58(%rbp)
  10.     0x1000022b4 <+ 148>: callq  0x100003ba2               ; symbol stub for: swift_release
  • 其中、swift_retain表示对当前强引用计数+1、swift_release为强引用计数-1操作
  • 我们这里要看的时 swift_weakInit 函数、因此下面我们从源码处分析weak修饰变量后、做了怎样对操作?
    • 打开源码工程、定位到 HeapObject.cpp 我们查到如下函数

  
  1. WeakReference *swift::swift_weakInit( WeakReference *ref, HeapObject *value) {
  2. ref->nativeInit(value);
  3. return ref;
  4. }
  • 此时表明:我们定义一个weak变量、则编译器调用swfit_weakInit函数、而该函数是由WeakReference来调用;
    • 相当于weak在编译器声明的过程中就自定义了WeakReference对象:该对象主要目的就是用来管理当前的弱引用对象
    • 而该对象调用了其中的 nativeInit(value) 方法;传递了当前的HeapObject对象
  • 下面我们查看 nativeInit 具体操作了什么?

  
  1. void nativeInit( HeapObject *object) {
  2. auto side = object ? object->refCounts.formWeakReference() : nullptr;
  3. nativeValue.store( WeakReferenceBits(side), std::memory_order_relaxed);
  4. }
  • 如果当前对象不为空则调用object->refCounts.formWeakReference()方法、我们查看下 formWeakReference方法的实现

  
  1. // SideTableRefCountBits specialization intentionally does not exist.
  2. template <>
  3. HeapObjectSideTableEntry* RefCounts< InlineRefCountBits>::formWeakReference()
  4. {
  5. auto side = allocateSideTable( true);
  6. if (side)
  7. return side->incrementWeak();
  8. else
  9. return nullptr;
  10. }
  • 首先创建SideTable、如果创建成功、则对其执行 incrementWeak();
  • 查看allocateSideTable创建过程

  
  1. template <>
  2. HeapObjectSideTableEntry* RefCounts< InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
  3. {
  4. auto oldbits = refCounts.load( SWIFT_MEMORY_ORDER_CONSUME);
  5. // Preflight failures before allocating a new side table.
  6. if (oldbits.hasSideTable()) {
  7. // Already have a side table. Return it.
  8. return oldbits.getSideTable();
  9. }
  10. else if (failIfDeiniting && oldbits.getIsDeiniting()) {
  11. // Already past the start of deinit. Do nothing.
  12. return nullptr;
  13. }
  14. HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
  15. auto newbits = InlineRefCountBits(side);
  16. ....
  17. return side;
  18. }
  1. 首先拿到原有的引用计数 refCounts.load(SWIFT_MEMORY_ORDER_CONSUME)
  2. 然后通过 getHeapObject() 创建一个 HeapObjectSideTableEntry 实例对象 side
  3. 再把创建出来的 side对象地址给 InlineRefCountBits 、而InlineRefCountBits 就是我们分析强引用计数的 RefCountBitsT,下面我们查看其初始化方法

  
  1. RefCountBitsT( HeapObjectSideTableEntry* side)
  2. : bits((reinterpret_cast< BitsType>(side) >> Offsets:: SideTableUnusedLowBits)
  3. | ( BitsType( 1) << Offsets:: UseSlowRCShift)
  4. | ( BitsType( 1) << Offsets:: SideTableMarkShift))
  5. {
  6. assert(refcountIsInline);
  7. }
  • 由上初始化方法可见、当前引用计数是通过创建并传递的side地址、直接将side地址做了偏移操作、存放到内存中。
  • 向右偏移SideTableUnusedLowBits位、也就是3位、然后将第63位上 UseSlowRC置1、将第62位上SideTableMark置1
    • 此时意味着将side地址偏移后存放到我们的uint64_t地址上
    • 综上引用计数位上的数值由此而来。
  • 此时我们查看 HeapObjectSideTableEntry类结构

  
  1. class HeapObjectSideTableEntry {
  2. std::atomic< HeapObject*> object;
  3. SideTableRefCounts refCounts;
  4. public:
  5. HeapObjectSideTableEntry( HeapObject *newObject)
  6. : object(newObject), refCounts()
  7. { }
  8. ......
  9. }
  • 其中、有引用计数对象 SideTableRefCounts、而SideTableRefCounts模版类如下
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
  • 而起作用的 SideTableRefCountBits 继承于 RefCountBitsT, 其中保留了父类的bits且还包含了新增的 weakBits

  
  1. class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>{
  2. uint32_t weakBits;
  3. ......
  4. }
    • 如果要还原引用计数、则需要将62位、63位清零、然后左移3位;最后得到弱引用信息、x/4g 则第三位对应得上强引用计数 、第四位对应为弱引用计数

  
  1. (lldb) po t
  2. < HSTeacher: 0x100626fc0>
  3. (lldb) x/4g 0x100626fc0
  4. 0x100626fc0: 0x0000000100008210 0xc0000000200c4e9c
  5. 0x100626fd0: 0x0000000000000013 0x000000006f6c6f48
  6. (lldb) x/4g 0x1006274E0 //转换后的数值
  7. // 对象地址
  8. 0x1006274e0: 0x0000000100626fc0 0x900000001006236b
  9. // 强引用计数 弱引用计数
  10. 0x1006274f0: 0x0000000200000002 0x0000000000000002

 


转载:https://blog.csdn.net/SharkToping/article/details/117253706
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场