
背景
前面swift类对象和示例对象探究文章,我们知道它们的底层逻辑以及内存布局,这篇文章我们再探究下swift的内存管理
值类型内存
js
var var1 = StructMemoryTest()
print("var1:")
print(withUnsafePointer(to: &var1) {ptr in print(ptr)})
var var2 = var1
print("var2: ")
print(withUnsafePointer(to: &var2) {ptr in print(ptr)})
var var3 = var2
print("var3:")
print(withUnsafePointer(to: &var3) {ptr in print(ptr)})
运行: 可以看到对于结构体值类型的赋值是深拷贝
再深入探究下底层逻辑
js
import Foundation
struct Student {
var name:String
var age:Int
}
var stu = Student(name: "小李", age: 28)
我们发现可以直接调用Student的初始化方法,但是我们并没有写Student的初始化方法 编译为sil,发现实际底层给添加了初始化方法
再通过一个例子我们修复test2的age,test1的age也不会受影响
js
struct StructMemoryTest {
var name:String?
var age:Int?
}
var test1 = StructMemoryTest(name: "小李", age: 28)
var test2 = test1
test2.age = 30
print("test1.age:\(test1.age) test2.age:\(test2.age)")

总结值类型
- 值类型存储在栈空间
- 值类型的赋值都是值传递,深拷贝
引用类型内存
引用类型分强弱引用,下面我们都分别看下实现原理
强引用
js
class ClassMemoryTest {
var name:String?
var age:Int?
}
var t = ClassMemoryTest()
var t1 = t
运行看t赋值给t1,它们的内存地址是不一样的
再看下类的内存布局 前面章节我们知道swift类的前16位分别是元类和引用计数相关数据
说明:
- 直接将对象赋值给一个变量,就是强引用
- 可以查看此时的refCounts的值
1.refCounts的认识
通过在底层源码中查找HeapObject来查看对象引用的计数。 引用计数refCounts属性其本质是RefCountsInt类型,包含unit32_t Type和int32_t SignedType
👇1.1 HeapObject
js
// HeapObject头文件中不被标准的Objective-C实例共享的成员
// The members of the HeapObject header that are not shared by a
// standard Objective-C instance
#define SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS \
InlineRefCounts refCounts
/// The Swift heap-object header.
/// This must match RefCountedStructTy in IRGen.
struct HeapObject {
/// This is always a valid pointer to a metadata object.
/// 总是一个指向元类的指针
HeapMetadata const *__ptrauth_objc_isa_pointer metadata;
// 引用计数相关
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
// 构造函数
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
...
}
说明:
- 对象在底层是HeapObject
- 所以在源码中查看HeapObject,发现引用是SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS
- 通过SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS的定义可以看到是refCounts的类型是InlineRefCounts
- HeapObject的构造函数执行插传入引用计数位InlineRefCounts::Initialized
👇1.2 InlineRefCounts
js
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
...
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
}
- 查看InlineRefCounts,发现它是RefCounts的别名
- 而RefCounts的成员是refCounts,它是由RefCountBits决定的
- 因此是InlineRefCounts通过InlineRefCountBits来决定里面的值
👇1.2.1 InlineRefCountBits
js
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
- InlineRefCountBits是RefCountBitsT的别名,传入RefCountIsInline
👇1.2.2 RefCountBitsT
js
// 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;
...
}
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位整型的数组。
2. refCounts的赋值探索
前面文章提高对象创建过程,swift源码是从swift_allocObject开始的
js
static HeapObject *_swift_allocObject_(HeapMetadata const *metadata,
size_t requiredSize,
size_t requiredAlignmentMask) {
assert(isAlignmentMask(requiredAlignmentMask));
auto object = reinterpret_cast<HeapObject *>(
swift_slowAlloc(requiredSize, requiredAlignmentMask));
// NOTE: this relies on the C++17 guaranteed semantics of no null-pointer
// check on the placement new allocator which we have observed on Windows,
// Linux, and macOS.
::new (object) HeapObject(metadata);
...
return object;
}
// HeapObject构造函数
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
- 通过swift_allocObject方法来构造对象,因此在这里会进行对象中引用属性的赋值
- 可以看到是通过refCounts来给引用计数赋值的,传入的参数是Initialized
👇2.1 Initialized
js
enum Initialized_t { Initialized };

- 进入Initialized定义,是一个枚举
- 其对应的refCounts方法中,看出真正干事的是RefCountBits
👇 2.2 RefCountBits
js
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
...
}
- 进入RefCountBits定义,也是一个模板定义
👇 2.2 RefCountBitsT
js
SWIFT_ALWAYS_INLINE
constexpr
RefCountBitsT(uint32_t strongExtraCount, uint32_t unownedCount)
: bits((BitsType(strongExtraCount) << Offsets::StrongExtraRefCountShift) |
(BitsType(1) << Offsets::PureSwiftDeallocShift) |
(BitsType(unownedCount) << Offsets::UnownedRefCountShift))
{ }
- 这里就是真正的初始化地方
- 实际上是做了一个位域操作
👇2.3 RefCountsBit结构 经过上文的查找,可以得到RefCountsBit结构
- isImmortal(0)
- UnownedRefCount(1-31):unowned的引用计数
- isDeinitingMask(32):是否进行释放操作
- StrongExtraRefCount(33-62):强引用计数
- UseSlowRC(63)
总结设置引用计数
设置引用计数实际是对底层的RefCountsBit数据结构进行位域操作
👇2.4 创建对象查看引用计数
js
class Teacher {
func teach() {
print("教语文课")
}
}
var teacher = Teacher()
var t1= teacher
2.4.1 第一次创爱对象
- 先创建一个全局的实例变量
- 之后创建对象
- 将对象赋值给全局变量
2.4.2 第二次创建对象
- 创建t1
- 把t1赋值给%9
- 把teacher %3 赋值为 %10
- copy_addr 将 %10 赋值为 %9
- %new = load $*LGTeacher(拿到这个对象)
- strong_retain %new(给这个对象引用计数加一)
- store %new to %9(将这个对象赋给t1) copy_addr最主要的就是对这个对象做了一下strong_ratain。之后再进行赋值操作
2.4.3 swift_retain的认识
strong_retain对应的就是 swift_retain,其内部是一个宏定义,内部是_swift_retain_,其实现是对object的引用计数作+1操作。
js
SWIFT_ALWAYS_INLINE
static HeapObject *_swift_retain_(HeapObject *object) {
SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
if (isValidPointerForNativeRetain(object))
object->refCounts.increment(1);
return object;
}
...
// Increment the reference count.
SWIFT_ALWAYS_INLINE
void increment(uint32_t inc = 1) {
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// 如果需要新增计数不等于1 并且 旧的bits是永生的 ->不处理后续逻辑
// 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来实现计数的
2.4.5 incrementStrongExtraRefCount
js
// 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).
SWIFT_NODISCARD SWIFT_ALWAYS_INLINE bool
incrementStrongExtraRefCount(uint32_t inc) {
// 对inc做强制类型转换为 BitsType // 其中 BitsType(inc) << Offsets::StrongExtraRefCountShift 等价于 1<<33位,16进制为 0x200000000 //这里的 bits += 0x200000000,将对应的33-63转换为10进制,为
// This deliberately overflows into the UseSlowRC field.
bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
return (SignedBitsType(bits) >= 0);
}
- 通过位域进行偏移
- 我们知道refCounts中的62-33位是strongCount,这里的位域目的就是将引用计数加在这里
- 每次的增加为1左移33位
- 也就是每次增加0x200000000
- 也就是BitsType(inc) << Offsets::StrongExtraRefCountShift
总结:
- swift中创建实例对象时默认为1
- 强引用计数存储在refCounts中的33-62位,因此每次引用计数+1,都要加上0x200000000
弱引用
主要认识弱引用的使用、以及散列表的存储结构。散列表不仅存储了弱引用,还有强引用和无主引用,如果有弱引用,在refCounts中直接使用散列表的地址
弱引用的使用
js
class Student {
var name:String = ""
var age:Int = 0
}
var stu = Student()
weak var weakStu = stu
- 给变量t赋值给弱引用t1
- 弱引用声明的变量是一个可选值,因为在程序运行过程中是允许将当前变量设置为nil的
debug调试看下汇编代码
- 前面我们知道当把对象赋值另外一个变量时,底层会执行swift_allocObject析建变量承接数据,这里当把对象赋值为一个weak修饰的变量时,底层走swift_weakInit
swift_weakInit
js
WeakReference *swift::swift_weakInit(WeakReference *ref, HeapObject *value) {
ref->nativeInit(value);
return ref;
}
参数
参数 | 含义 |
---|---|
ref | 弱引用指针 |
value | 弱引用指针指向的宿主对象 |
- 查看swift_weakInit,可以看到系统本身提供了一个WeakReference类,用来进行引用计数的创建
- 这个ref对象调用nativeInit,传入对象,就可以创建弱引用了
若引用对象的nativeInit函数
js
void nativeInit(HeapObject *object) {
auto side = object ? object->refCounts.formWeakReference() : nullptr;
nativeValue.store(WeakReferenceBits(side), std::memory_order_relaxed);
}
- 调用宿主对象的refCounts的formWeakReference完成弱引用映射
宿主对象的refCounts的formWeakReference
js
// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
auto side = allocateSideTable(true);
if (side)
return side->incrementWeak();
else
return nullptr;
}
- 创建散列表
- 通过incrementWeak()给散列表增加弱引用
allocateSideTable创建散列表
js
// 返回一个对象的side table散列表,如果有必要再创建
// Return an object's side table, allocating it if necessary.
// 如果对象在执行析构函数返回一个null出去
// Returns null if the object is deiniting.
// SideTableRefCountBits specialization intentionally does not exist.
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
//1、先拿到原本的引用计数
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
//2. 预检查操作
//在创建新的side table之前判断旧的引用计数是否已经使用散列表side table 如果存在返回已经存在的side table
// Preflight failures before allocating a new side table.
if (oldbits.hasSideTable()) {
// Already have a side table. Return it.
return oldbits.getSideTable();
}
// 如果宿主对象正在执行析构函数直接返回null
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
// Already past the start of deinit. Do nothing.
return nullptr;
}
// Preflight passed. Allocate a side table.
//3. 创建sidetable
// FIXME: custom side table allocator
//创建sideTable
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
//将创建的地址给到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;
}
- 先拿到原本的引用计数
- 散列表也需要存储强引用的引用计数
- 如果这个bits本来就有散列表了,就直接使用原有的散列表
- 创建散列表(通过allocateSideTable构建散列表)
- 这个散列表只是针对当前对象的
- 通过InlineRefCountBits将散列表的地址赋给值newBits
- 因此如果有弱引用,那么引用计数中存储的就是散列表地址
InlineRefCountBits
js
SWIFT_ALWAYS_INLINE
RefCountBitsT(HeapObjectSideTableEntry* side)
//将散列表通过地址偏移操作存入RefCountBits中
: bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
| (BitsType(1) << Offsets::UseSlowRCShift)
| (BitsType(1) << Offsets::SideTableMarkShift))
{
assert(refcountIsInline);
}
- 传入的参数是HeapObjectSideTableEntry的方法,就是用来构建散列表的Bits
- 将散列表地址通过偏移操作存储到RefCountBits内存中
- 在这里可以看到,除去63、62位,剩下的地方用来存储了散列表地址
HeapObjectSideTableEntry
js
class HeapObjectSideTableEntry {
// FIXME: does object need to be atomic?
std::atomic<HeapObject*> object; //对象
SideTableRefCounts refCounts; //散列表计数
...
}
- 查看散列表的结构
- 包括object和refCounts
- 其实可以看做分别将对象和引用计数作为键和值
- 这里的引用计数表就是散列表
SideTableRefCountBits
- 继承自RefCountBitsT
- 只有一个属性weakBits,是弱引用属性
- 在RefCountBitsT中还有一个属性,64位用来记录原有的强引用计数。
验证
计算过程
- 散列表地址 = refCounts 清除63、62位 + 左移三位
- 上面我们拿到了弱引用后的地址0xc000000020261de6
- 将62、63清零,变成0x20261DE6
- 然后左移3位变成0x10130EF30,这个就是散列表地址
- 查看散列表内容
- 包含两个内容,一个是对象,所以会打印对象的地址
- 一个是引用表,引用表包含强引用计数和弱引用计数
总结弱引用:
- 当给一个对象增加弱引用后,会创建一个散列表用来存储弱引用和强引用
- 并且将散列表的地址存储到bits中
整体总结
- 值类型都是神拷贝操作
- HeapObject有两种refCounts方式
- 没有弱引用
- 没有弱引用,只有强引用和无主引用
- strongCount + unonwnedCount
- 有弱引用
- 如果有弱引用,那么存储的是一个散列表地址
- 而散列表中存储的是object和散列表信息
- 散列表信息又包含了weakBits
- 同时又因为继承RefCountBitsT,所以依然会包含强引用和无主引用。
- 没有弱引用
到这里就完毕了,感谢您的阅读,欢迎阅读我的其他文章!