Swift中内存管理使⽤⾃动引⽤计数(ARC)机制来追踪和管理内存。
下面我们来看强引用案例
-
class HSTeacher{
-
var age:
Int =
29
-
var name:
String =
"Hellon"
-
}
-
var t =
HSTeacher()
-
var t1 = t
-
var t2 = t
-
print(
"end")
//断点
- 控制台在断点时、输出t变量的地址、然后格式化输出地址得
-
(lldb) po t
-
<
LGTeacher:
0x1006523d0>
-
(lldb) x/8g
0x1006523d0
-
0x1006523d0:
0x000000010000c420
0x0000000600000002
-
0x1006523e0:
0x000000000000001d
0x00006e6f6c6c6548
-
0x1006523f0:
0xe600000000000000
0x00000001006524f0
-
0x100652400:
0x0000000000000000
0x0000000000000000
- 经过前面Swift创建对象的探索、我们得到refCounts 为 0x0000000600000002、然而这和我们所理解的引用计数个数不太一致、
下面我们进入源码分析
- 直接定位到HeapObject.cpp中、点击HeapObject对象、我们可以看到其结构体结构为
-
struct HeapObject {
-
/// This is always a valid pointer to a metadata object.
-
HeapMetadata const *metadata;
-
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
-
....
-
};
- 下面我们进入到其中的宏定义 SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS 中
-
// The members of the HeapObject header that are not shared by a
-
// standard Objective-C instance
-
#define
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
-
InlineRefCounts refCounts
- 这就是我们的引用计数 refCounts了
- 继续开始探索、定义refCounts的这个类 InlineRefCounts
-
typedef
RefCounts<
InlineRefCountBits>
InlineRefCounts;
-
typedef
RefCounts<
SideTableRefCountBits>
SideTableRefCounts;
- 于是乎、InlineRefCounts 被起了个别名为 RefCounts、下面开始看 RefCounts类结构
-
template <typename
RefCountBits>
//模版类
-
class RefCounts {
-
std::atomic<
RefCountBits> refCounts;
-
......
-
public:
-
enum Initialized_t {
Initialized };
-
enum Immortal_t {
Immortal };
-
RefCounts() =
default;
-
// Refcount of a new object is 1.
-
constexpr
RefCounts(
Initialized_t)
-
: refCounts(
RefCountBits(
0,
1)) {}
-
// Refcount of an immortal object has top and bottom bits set
-
constexpr
RefCounts(
Immortal_t)
-
: refCounts(
RefCountBits(
RefCountBits::
Immortal)) {}
-
.......
-
}
- 由RefCounts类的上边、我们发现它是个模版类、那么真正起作用的是传进来的模版参数、
typedef RefCounts<InlineRefCountBits> InlineRefCounts
- 由上述类型别名定义可得模版参数为 InlineRefCountBits、探索该类的结构如下
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
- 万万没想到、又是一个模版类、那么我们直接就看 RefCountIsInline是个什么东西?
-
// RefCountIsInline: refcount stored in an object
-
// RefCountNotInline: refcount stored in an object's side table entry
-
enum RefCountInlinedness {
RefCountNotInline =
false,
RefCountIsInline =
true };
- 终于看到了个正常的玩意、RefCountIsInline表示对象中的refCount。RefCountInlinedness 是个枚举、要么false要么true
- 回过头来、我们继续看RefCountBitsT 是个什么东西呢?
-
// Basic encoding of refcount and flag data into the object's header.
-
template <
RefCountInlinedness refcountIsInline>
-
class RefCountBitsT {
-
friend
class RefCountBitsT<RefCountIsInline>;
-
friend class RefCountBitsT<RefCountNotInline>;
-
static const RefCountInlinedness Inlinedness = refcountIsInline;
-
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
-
BitsType;
-
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::SignedType
-
SignedBitsType;
-
typedef RefCountBitOffsets<sizeof(BitsType)>
-
Offsets;
-
BitsType bits;
-
.....
-
}
- 对上述类的分析、我们需要看其成员变量 BitsType bits; 而它本身又是一个类内部的宏定义取别名
-
typedef typename
RefCountBitsInt<refcountIsInline,
sizeof(void*)>::
Type
-
BitsType;
- 其中的RefCountBitsInt结构为如下
-
template <
RefCountInlinedness refcountIsInline>
-
struct RefCountBitsInt<refcountIsInline, 8> {
-
typedef uint64_t
Type;
-
typedef int64_t
SignedType;
-
};
- 那么本质上 BitsType就是 将RefCountBitsInt内部的uint64_t Type取的别名
- 下面我们回到 HeapObject 结构体中
-
struct HeapObject {
-
/// This is always a valid pointer to a metadata object.
-
HeapMetadata const *metadata;
-
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
-
#ifndef __swift__
-
HeapObject() =
default;
-
//根据新分配的对象初始化HeapObject
-
constexpr
HeapObject(
HeapMetadata const *newMetadata)
-
: metadata(newMetadata)
-
, refCounts(
InlineRefCounts::
Initialized)
-
{ }
-
.....
-
};
- 初始化方法中、我们传入了 newMetadata、Initialized、其中的Initialized 、我们可以发现其实它是一个enum
enum Initialized_t { Initialized };
- 并且其中的 Initialized_t 使用在引用计数上、并且表明新建的对象其Refcount为 1
-
constexpr
RefCounts(
Initialized_t)
-
: refCounts(
RefCountBits(
0,
1)) {}
- 其中使用的RefCountBits(0,1)、让我们回到了 RefCountBitsT模版定义类上、所以我们着重分析该类的结构
- 首先查找该类的初始化方法、根据其中传入的(0,1)参数定位到如下初始化方法
-
LLVM_ATTRIBUTE_ALWAYS_INLINE
-
constexpr
-
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
-
: bits((
BitsType(strongExtraCount) <<
Offsets::
StrongExtraRefCountShift) |
-
(
BitsType(unownedCount) <<
Offsets::
UnownedRefCountShift))
-
{ }
- 由该初始化方法、我们看出:对传入的0、1参数进行类按位偏移的操作、Offsets结构为
-
typedef
RefCountBitOffsets<
sizeof(
BitsType)>
-
Offsets;
- 由此我们看出、Offsets的偏移量取决于成员变量BitsType属于哪种类型
-
template <>
-
struct RefCountBitOffsets<8> {
-
static const size_t
IsImmortalShift =
0;
-
static const size_t
IsImmortalBitCount =
1;
-
static const uint64_t
IsImmortalMask = maskForField(
IsImmortal);
-
-
static const size_t
UnownedRefCountShift = shiftAfterField(
IsImmortal);
-
static const size_t
UnownedRefCountBitCount =
31;
-
static const uint64_t
UnownedRefCountMask = maskForField(
UnownedRefCount);
-
-
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);
-
};
- 由源码指示得到强引用计数时
- IsImmortal 从第0位开始、占用1个字节
- UnownedRefCount 从第1位开始、占用31字节
- IsDeiniting 从第32位开始、占用1字节
- StrongExtraRefCount 从第33位开始、占用30字节
- UseSlowRC 从第63位开始、占用1字节
因此我们汇总得到
-
位数
Type
-
0
IsImmortal
-
1-
31
UnownedRefCount
-
32
IsDeiniting
-
33-
62
StrongExtraRefCount
-
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 函数 内部
-
// main
-
sil @main : $
@convention(
c) (
Int32,
UnsafeMutablePointer<
Optional<
UnsafeMutablePointer<
Int8>>>) ->
Int32 {
-
bb0(%
0 : $
Int32, %
1 : $
UnsafeMutablePointer<
Optional<
UnsafeMutablePointer<
Int8>>>):
-
alloc_global @$s4main1tAA9HSTeacherCvp
// id: %2
-
%
3 = global_addr @$s4main1tAA9HSTeacherCvp : $*
HSTeacher
// users: %7, %15, %10
-
%
4 = metatype $@thick
HSTeacher.
Type
// user: %6
-
// function_ref HSTeacher.__allocating_init()
-
%
5 = function_ref @$s4main9HSTeacherCACycfC : $
@convention(method) (@thick
HSTeacher.
Type) -> @owned
HSTeacher
// user: %6
-
%
6 = apply %
5(%
4) : $
@convention(method) (@thick
HSTeacher.
Type) -> @owned
HSTeacher
// user: %7
-
store %
6 to %
3 : $*
HSTeacher
// id: %7
-
alloc_global @$s4main2t1AA9HSTeacherCvp
// id: %8
-
//拿到全局变量地址
-
%
9 = global_addr @$s4main2t1AA9HSTeacherCvp : $*
HSTeacher
// user: %11
-
%
10 = begin_access [read] [
dynamic] %
3 : $*
HSTeacher
// users: %12, %11
-
//先去加载当前的值、 %new = load $*HSTeacher
-
//编译器会默认 strong_retain %new
-
//把我们当前变量中的内容赋值到 %9
-
copy_addr %
10 to [initialization] %
9 : $*
HSTeacher
// id: %11
-
end_access %
10 : $*
HSTeacher
// id: %12
-
alloc_global @$s4main2t2AA9LGTeacherCvp
// id: %13
-
.......
-
}
- 没错、关键点在 copy_addr关键字、就是这个导致每赋值一次就调用一次strong_retain、本质上其实调用了 swift_retain函数。
- 下面、接下来、最重要的是:再回到源码、我们搜索swift_retain具体实现、来到HeapObject.cpp
- 一些专有的内部调用
-
HeapObject *swift::swift_retain(
HeapObject *object) {
-
CALL_IMPL(swift_retain, (object));
-
}
-
HeapObject *(*swift::_swift_retain)(
HeapObject *object) = _swift_retain_;
- 最后我们得到这样 swift_retain ---> _swift_retain_ 、找它!!!!
-
static
HeapObject *_swift_retain_(
HeapObject *object) {
-
SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
-
if (isValidPointerForNativeRetain(object))
-
object->refCounts.increment(
1);
-
return object;
-
}
- 惊喜在这咯、refCounts.increment(1)操作、
-
// Increment the reference count.
-
void increment(uint32_t inc =
1) {
-
auto oldbits = refCounts.load(
SWIFT_MEMORY_ORDER_CONSUME);
-
RefCountBits newbits;
-
do {
-
newbits = oldbits;
-
bool fast = newbits.incrementStrongExtraRefCount(inc);
-
if (
SWIFT_UNLIKELY(!fast)) {
-
if (oldbits.isImmortal())
-
return;
-
return incrementSlow(oldbits, inc);
-
}
-
}
while (!refCounts.compare_exchange_weak(oldbits, newbits,
-
std::memory_order_relaxed));
-
}
- 我们再看 incrementStrongExtraRefCount 对这个 inc 做了什么?inc = 1
-
// 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);
-
}
- BitsTyp 对 inc 做了强制类型转换、转为uint64_t类型、接下来将其偏移存放到 StrongExtraRefCount内存地址上、
- bits作为RefCountBitsT类中的成员变量、一直在做 + 操作。
- 所以每一次的swift_retain操作、都是在增加引用计数、也就是类的实例对象每一次的赋值操作都是在增加引用计数。
下面分析弱引用计数
-
class HSTeacher {
-
var age:
Int =
19
-
var name:
String =
"Holo"
-
deinit {
-
print(
"HSTeacher deinit")
-
}
-
}
-
var t =
HSTeacher()
-
weak
var t1 = t
//断点1
-
print(
"end")
//断点2
- 当我们使用weak修饰变量时、并不会对当前强引用产生影响、
- 查看 t 引用计数、发现其引用计数位发生改变
-
(lldb) v t
-
(
SwiftExecite.
HSTeacher) t =
0x0000000100520480 (age =
19, name =
"Holo")
-
(lldb) x/4g
0x0000000100520480
-
0x100520480:
0x0000000100008210
0x0000000000000002
-
0x100520490:
0x0000000000000013
0x000000006f6c6f48
-
(lldb) x/4g
0x0000000100520480
-
0x100520480:
0x0000000100008210
0xc0000000200c6b78
-
0x100520490:
0x0000000000000013
0x000000006f6c6f48
- 然而当前获取t变量存放的强引用计数、发现其本质上并没有发生改变
-
(lldb) po
CFGetRetainCount(t)
-
2
- 顾我们来看weak修饰后的变量t1、编译器提示为可选类型、
- t = nil 时 'nil' cannot be assigned to type 'HSTeacher'
- t1 = nil时、则编译器不会报错。
- 表明当前弱引用并不会对当前t实例对象保持强引用、顾不会阻止强引用计数为0时释放该对象
- 运行代码、在断点1处我们查看到汇编
-
0x10000228a <+
106>: callq
0x100003ba8 ; symbol stub
for: swift_retain
-
0x10000228f <+
111>: leaq -
0x20(%rbp), %rdi
-
0x100002293 <+
115>: movq %rax, -
0x50(%rbp)
-
0x100002297 <+
119>: callq
0x100003b9c ; symbol stub
for: swift_endAccess
-
0x10000229c <+
124>: leaq
0x60cd(%rip), %rdi ;
SwiftExecite.t1 :
Swift.
Optional<
SwiftExecite.
HSTeacher>
-
0x1000022a3 <+
131>: movq -
0x48(%rbp), %rsi
-
0x1000022a7 <+
135>: callq
0x100003bae ; symbol stub
for: swift_weakInit
-
0x1000022ac <+
140>: movq -
0x48(%rbp), %rdi
-
0x1000022b0 <+
144>: movq %rax, -
0x58(%rbp)
-
0x1000022b4 <+
148>: callq
0x100003ba2 ; symbol stub
for: swift_release
- 其中、swift_retain表示对当前强引用计数+1、swift_release为强引用计数-1操作
- 我们这里要看的时 swift_weakInit 函数、因此下面我们从源码处分析weak修饰变量后、做了怎样对操作?
- 打开源码工程、定位到 HeapObject.cpp 我们查到如下函数
-
WeakReference *swift::swift_weakInit(
WeakReference *ref,
HeapObject *value) {
-
ref->nativeInit(value);
-
return ref;
-
}
- 此时表明:我们定义一个weak变量、则编译器调用swfit_weakInit函数、而该函数是由WeakReference来调用;
- 相当于weak在编译器声明的过程中就自定义了WeakReference对象:该对象主要目的就是用来管理当前的弱引用对象
- 而该对象调用了其中的 nativeInit(value) 方法;传递了当前的HeapObject对象
- 下面我们查看 nativeInit 具体操作了什么?
-
void nativeInit(
HeapObject *object) {
-
auto side = object ? object->refCounts.formWeakReference() : nullptr;
-
nativeValue.store(
WeakReferenceBits(side), std::memory_order_relaxed);
-
}
- 如果当前对象不为空则调用object->refCounts.formWeakReference()方法、我们查看下 formWeakReference方法的实现
-
// SideTableRefCountBits specialization intentionally does not exist.
-
template <>
-
HeapObjectSideTableEntry*
RefCounts<
InlineRefCountBits>::formWeakReference()
-
{
-
auto side = allocateSideTable(
true);
-
if (side)
-
return side->incrementWeak();
-
else
-
return nullptr;
-
}
- 首先创建SideTable、如果创建成功、则对其执行 incrementWeak();
- 查看allocateSideTable创建过程
-
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;
-
}
-
HeapObjectSideTableEntry *side = new
HeapObjectSideTableEntry(getHeapObject());
-
auto newbits =
InlineRefCountBits(side);
-
....
-
return side;
-
}
- 首先拿到原有的引用计数 refCounts.load(SWIFT_MEMORY_ORDER_CONSUME)
- 然后通过 getHeapObject() 创建一个 HeapObjectSideTableEntry 实例对象 side
- 再把创建出来的 side对象地址给 InlineRefCountBits 、而InlineRefCountBits 就是我们分析强引用计数的 RefCountBitsT,下面我们查看其初始化方法
-
RefCountBitsT(
HeapObjectSideTableEntry* side)
-
: bits((reinterpret_cast<
BitsType>(side) >>
Offsets::
SideTableUnusedLowBits)
-
| (
BitsType(
1) <<
Offsets::
UseSlowRCShift)
-
| (
BitsType(
1) <<
Offsets::
SideTableMarkShift))
-
{
-
assert(refcountIsInline);
-
}
- 由上初始化方法可见、当前引用计数是通过创建并传递的side地址、直接将side地址做了偏移操作、存放到内存中。
- 向右偏移SideTableUnusedLowBits位、也就是3位、然后将第63位上 UseSlowRC置1、将第62位上SideTableMark置1
- 此时意味着将side地址偏移后存放到我们的uint64_t地址上
- 综上引用计数位上的数值由此而来。
- 此时我们查看 HeapObjectSideTableEntry类结构
-
class HeapObjectSideTableEntry {
-
std::atomic<
HeapObject*> object;
-
SideTableRefCounts refCounts;
-
public:
-
HeapObjectSideTableEntry(
HeapObject *newObject)
-
: object(newObject), refCounts()
-
{ }
-
......
-
}
- 其中、有引用计数对象 SideTableRefCounts、而SideTableRefCounts模版类如下
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
- 而起作用的 SideTableRefCountBits 继承于 RefCountBitsT, 其中保留了父类的bits且还包含了新增的 weakBits
-
class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>{
-
uint32_t weakBits;
-
......
-
}
-
- 如果要还原引用计数、则需要将62位、63位清零、然后左移3位;最后得到弱引用信息、x/4g 则第三位对应得上强引用计数 、第四位对应为弱引用计数
-
(lldb) po t
-
<
HSTeacher:
0x100626fc0>
-
(lldb) x/4g
0x100626fc0
-
0x100626fc0:
0x0000000100008210
0xc0000000200c4e9c
-
0x100626fd0:
0x0000000000000013
0x000000006f6c6f48
-
(lldb) x/4g
0x1006274E0
//转换后的数值
-
// 对象地址
-
0x1006274e0:
0x0000000100626fc0
0x900000001006236b
-
// 强引用计数 弱引用计数
-
0x1006274f0:
0x0000000200000002
0x0000000000000002
转载:https://blog.csdn.net/SharkToping/article/details/117253706
查看评论