提到内存管理在iOS开发中,就不得不提ARC(自动引用技术)。本文主要讨论的就是ARC在swift中是如何存储、计算,以及循环引用是如何解决的。
toc
一, refCount引用计数(强引用 无主引用)
先看一段简单的代码
代码语言:javascript复制class classModel{
var age : Int = 18
}
func test() {
let c = classModel()
var c1 = c
var c2 = c
}
test()
通过LLDB
添加断点查看当前c
对象的内存情况
图一
- 通过经验该对象的引用计数应该是:3
- 可是
图一
中对象内存中refCopunt:0x0000000600000002
,以及通过cfGetRetainCount(AnyObject)获取到的引用计算看起来都是不正确的。
1. cfGetRetainCount - sil解析
代码语言:javascript复制class classModel{
var age : Int = 18
}
let temp = classModel()
CFGetRetainCount(temp)
编译后的Sil文件:
图二
- 通过
图二
的sil
文件很简单的看出CFGetRetainCount
在调用之前对temp
这个变量进行了一次强引用
,也就是引用计数加1
。所以通过CFGetRetainCount
获得的引用计数需要-1
才是正确
的。这也印证了之前的经验推论。
2. refCount - 类型的源码
swift底层探索 01 - 类初始化&类结构一文中有对swift类的源码进行过简单的解释。
相信你一定会有疑惑:0x0000000600000002
是什么?它为什么被叫做refCount
,探索方法依旧是翻开源码!
- 由于源码中涉及
多层嵌套 模板类 泛型
,所以阅读起来还是有点困难的,建议自己动手试试。swift-5.3.1源码地址
(1) 该方法是swift对象
的初始化方法
代码语言:javascript复制 constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
- 其中
refCounts(InlineRefCounts::Initialized)
就是refCounts
的初始化方法. InlineRefCounts
是refCounts
的类型.
(2) InlineRefCounts类型
代码语言:javascript复制typedef RefCounts<InlineRefCountBits> InlineRefCounts;
- InlineRefCounts是重命名
InlineRefCounts = RefCounts
(3) RefCounts类
代码语言:javascript复制template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
//省略方法
}
RefCounts
是依赖泛型:RefCountBits
的模板类。同时发现refCounts
的类型也是泛型:RefCountBits
;- 通过第2步,第3步:
RefCounts = RefCountBits = InlineRefCountBits
(4) InlineRefCountBits类型
代码语言:javascript复制typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
- InlineRefCountBits也是重命名
InlineRefCountBits = RefCountBitsT
;
(5) RefCountIsInline枚举
代码语言:javascript复制enum RefCountInlinedness { RefCountNotInline = false, RefCountIsInline = true };
- 传入枚举值:
RefCountIsInline = true
(6) RefCountBitsT 核心类
代码语言:javascript复制template <RefCountInlinedness refcountIsInline>
class RefCountBitsT {
//内部变量
BitsType bits;
//内部变量类型
typedef typename RefCountBitsInt<refcountIsInline, sizeof(void*)>::Type
BitsType;
...
//省略无关代码
}
- 内部只有一个变量
bits
,类型为BitsType
(7) RefCountBitsInt 结构体
代码语言:javascript复制template <RefCountInlinedness refcountIsInline>
struct RefCountBitsInt<refcountIsInline, 8> {
typedef uint64_t Type;
typedef int64_t SignedType;
};
- 根据第6步的传参得到
RefCountBitsInt
结构,以及Type == uint64_t
(8) 【总结】
- 通过第1步,第2步,第3步,第4步:
InlineRefCounts = RefCounts = RefCountBits = InlineRefCountBits = RefCountBitsT
;(该关系并不严谨只是为了解释简单) - 通过第6步,第7步:
RefCountBitsT
中bits
类型是:uint64_t
; refCounts
的类型为RefCountBitsT
,内部只有一个变量bits
类型为uint64_t
;- 而
RefCountBitsT
是模板类,首地址指向唯一内部变量bits
; - 结论为:**
uint64_t : refCounts
**.
3. refCount - 初始化的源码
现在再看0x0000000600000002
知道它是一个uint64_t
的值,可是内部存储了哪些值还需要查看初始化方法
,观察初始化方法做了什么?
(1) 该方法是swift对象
的初始化方法
代码语言:javascript复制 constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
Initialized
初始化
(2) RefCounts
的初始化方法
代码语言:javascript复制template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
enum Initialized_t { Initialized };
constexpr RefCounts(Initialized_t)
: refCounts(RefCountBits(0, 1)) {}
...
//省略无关代码
}
- 调用了
RefCountBits
的初始化方法,根据上一步中的关系对应:RefCountBits = InlineRefCountBits = RefCountBitsT
(3) RefCountBitsT
的初始化方法
代码语言:javascript复制 constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
(BitsType(1) << Offsets::PureSwiftDeallocShift) |
(BitsType(unownedCount) << Offsets::UnownedRefCountShift))
{ }
(4)Offsets对应关系
Offsets的关系图:undefined
简书-月月
(5)【总结】
0x0000000600000002
就可以拆分为: 5部分。强引用的引用计数位于:33-62
位
0x0000000600000002 >> 33 // 引用计数 = 3
- 同样满足之前的论证。
补充1:
- 初始化并且没有赋值时,
引用计数为0,无主引用数为:1
。源码中的确也是这样的RefCountBits(0, 1)
补充2:
代码语言:javascript复制class PersonModel{
var age : Int = 18
}
func test() {
let c = PersonModel()
var c1 = c
var c2 = c
var c3 = c
//增加了一个无主引用
unowned var c4 = c
}
test()
图三-输出结果
unowned
在本文的解决循环引用中会解释。- StrongExtraRefCountShift(33-63位) :
0x0000000800000004
右移33位 =4
- UnownedRefCountShift(1-31位) :
0x0000000800000004
左移32位,右移33位。 =2
4. 引用计数增加、减少
知道了引用计数的数据结构
和初始化值
,现在就需要知道引用计数是如何增加
和减少
,本文中以增加为例;
通过打开汇编,查看调用堆栈:
图三
- 发现会执行
swift_retain
这个函数
swift_retain源码
代码语言:javascript复制//入口函数
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))
//引用计数在该函数进行 1操作
object->refCounts.increment(1);
return object;
}
- 后面源码的阅读会进行
断点调试
的方式。
increment
图四
通过可执行源码进行调试可执行源码。
- 根据断点证实的确是执行到
increment
函数,并且新增值是1
具体计算的方法
图五
- 计算都是从
33位
开始计算的
二, refCount 循环引用
代码语言:javascript复制class PersonModel{
var teach : TeachModel?
}
class TeachModel{
var person : PersonModel?
}
面对这样的相互包含的两个类,使用时一定会出现相互引用(循环引用)
图六
deinit
方法没有调用,造成了循环引用。
1. weak关键字
通过OC的经验,可以将其中一个值改为weak,就可以打破循环引用.
代码语言:javascript复制class PersonModel{
weak var teach : TeachModel?
}
class TeachModel{
weak var person : PersonModel?
}
图六
- 很显然
weak
是可以的。问题是:weak做了什么呢?
2. weak 实现源码
代码语言:javascript复制weak var weakP = PersonModel()
依旧是打开汇编断点
.
图七
- 从图七能看出到weak是调用了
swift_weak
。
swift_weak源码
代码语言:javascript复制//weak入口函数
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
void nativeInit(HeapObject *object) {
//做一个非空判断
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
- 没有找到
WeakReference
对象的创建,猜测是编译器自动创建的用来管理weak动作
.
通过formWeakReference创建HeapObjectSideTableEntry
代码语言:javascript复制template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
auto side = allocateSideTable(true);
if (side)
return side->incrementWeak();
else
return nullptr;
}
调用allocateSideTable进行创建
代码语言:javascript复制template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
//获取当前对象的原本的引用计数(uInt64_t)
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
...
// FIXME: custom side table allocator
//创建HeapObjectSideTableEntry对象
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
//RefCountBitsT对象进行初始化
auto newbits = InlineRefCountBits(side);
do {
if (oldbits.hasSideTable()) {
auto result = oldbits.getSideTable();
delete side;
return result;
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
return nullptr;
}
side->initRefCounts(oldbits);
//通过地址交换完成赋值
} while (! refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return side;
}
- 最终将
RefCountBitsT
对象(class)的地址和旧值uint_64
进行交换。
HeapObjectSideTableEntry对象
代码语言:javascript复制class HeapObjectSideTableEntry {
std::atomic<HeapObject*> object;
SideTableRefCounts refCounts;
...
}
class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
//weak_count
uint32_t weakBits;
}
class RefCountBitsT {
//Uint64_t就是strong_count | unowned_count
BitsType bits;
}
通过源码分析得出HeapObjectSideTableEntry
对象的内存分布
RefCountBitsT初始化
最终保存到实例对象的refcount字段的内容(RefCountBitsT)创建
//Offsets::SideTableUnusedLowBits = 3
//SideTableMarkShift 高位 62位
//UseSlowRCShift 高位 63位
RefCountBitsT(HeapObjectSideTableEntry* side)
: bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
| (BitsType(1) << Offsets::UseSlowRCShift)
| (BitsType(1) << Offsets::SideTableMarkShift))
{
assert(refcountIsInline);
}
62位,63位改为0 -> 整体左移3位
: 就可以得到sideTable对象
的地址。
lldb验证
现在知道了refcount字段获取规律,以及sideTable对象
的内部结构,现在通过lldb验证一下。
图八
- 发现被weak修饰之后,refcount变化成
sideTable对象地址
高位标识符
图九
- 将高位62,63变为0后,在左移3位.
图十
0x10325D870
这就是sideTable对象地址
weak_count 增加
weakcount是从第二位开始计算的。
在formWeakReference
函数中出现了side->incrementWeak();
在sideTable对象
创建完成后调用了该函数.
HeapObjectSideTableEntry* incrementWeak() {
if (refCounts.isDeiniting())
return nullptr;
//没有销毁就调用
refCounts.incrementWeak();
return this;
}
void incrementWeak() {
//获取当前的sideTable对象
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));
}
void incrementWeakRefCount() {
//就是一个简单
weakBits ;
}
- 在声明weak后,调用了
incrementWeak
自增方法; - 在
incrementWeak
方法中获取了sideTable
对象; - 在
incrementWeakRefCount
完成了weakBits
的自增;
注:在weak引用之后,在进行strong强引用后,refCount该如何计算呢?篇幅问题就不展开了,各位可以自己试试。
三, 捕获列表
[weak t]
/[unowned t]
在swift中被称为捕获列表
。- 作用:
- 解决closure的循环引用;
- 进行外部变量的值捕获
本次换个例子。
代码语言:javascript复制class TeachModel{
var age = 18
var closure : (() -> Void)?
deinit {
print("deinit")
}
}
func test() {
let b = TeachModel()
b.closure = {
b.age = 1
}
print("end")
}
- 看到这段代码,deinit会不会执行呢?答案是很显然的,
实例对象的闭包和实例对象相互持有
,一定是不会释放
的。
作用1-解决循环引用
代码语言:javascript复制func test() {
let b = TeachModel()
b.closure = {[weak b] in
b?.age = 1
}
print("end")
}
func test() {
let b = TeachModel()
b.closure = {[unowned b] in
b?.age = 1
}
print("end")
}
执行效果,都可以解决循环引用:
- weak修饰之后对象会变为
作用2-捕获外部变量
例如这样的代码:
代码语言:javascript复制func test() {
var age = 18
var height = 1.8
var name = "Henry"
height = 2.0
//age,height被闭包进行了捕获
let closure = {[age, height] in
print(age)
print(height)
print(name)
}
age = 20
height = 1.85
name = "Wan"
//猜猜会输出什么?
closure()
}
age
,height
被捕获之后,值虽然被外部修改但不会影响闭包内的值
。- 闭包捕获的值时机为
闭包声明之前
。
闭包捕获之后值发生了什么?
通过打开汇编调试,并查看寄存器堆栈信息.
- 猜测
rdx-0x0000000100507e00
,存在堆区。而闭包外的age
是存在栈区的。