序言:各个社区有关 Objective-C weak 机制的实现分析文章有不少,然而 Swift 发布这么长时间以来,有关 ABI 的分析文章一直很是少,彷佛也是不少 iOS 开发者未涉及的领域… 本文就从源码层面分析一下 Swift 是如何实现 weak 机制的。git
因为 Swift 源码量较大,强烈建议你们把 repo clone 下来,结合源码一块儿来看这篇文章。github
$ git clone https://github.com/apple/swift.git
复制代码
Swift 整个工程采用了 CMake 做为构建工具,若是你想用 Xcode 来打开的话须要先安装 LLVM,而后用 cmake -G
生成 Xcode 项目。编程
咱们这里只是进行源码分析,我就直接用 Visual Studio Code 配合 C/C++ 插件了,一样支持符号跳转、查找引用。另外提醒一下你们,Swift stdlib 里 C++ 代码的类型层次比较复杂,不使用 IDE 辅助阅读起来会至关费劲。swift
下面咱们就正式进入源码分析阶段,首先咱们来看一下 Swift 中的对象(class
实例)它的内存布局是怎样的。安全
HeapObject
咱们知道 Objective-C 在 runtime 中经过 objc_object
来表示一个对象,这些类型定义了对象在内存中头部的结构。一样的,在 Swift 中也有相似的结构,那就是 HeapObject
,咱们来看一下它的定义:bash
struct HeapObject {
/// This is always a valid pointer to a metadata object.
HeapMetadata const *metadata;
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
HeapObject() = default;
// Initialize a HeapObject header as appropriate for a newly-allocated object.
constexpr HeapObject(HeapMetadata const *newMetadata)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Initialized)
{ }
// Initialize a HeapObject header for an immortal object
constexpr HeapObject(HeapMetadata const *newMetadata,
InlineRefCounts::Immortal_t immortal)
: metadata(newMetadata)
, refCounts(InlineRefCounts::Immortal)
{ }
};
复制代码
能够看到,HeapObject
的第一个字段是一个 HeapMetadata
对象,这个对象有着与 isa_t
相似的做用,就是用来描述对象类型的(等价于 type(of:)
取得的结果),只不过 Swift 在不少状况下并不会用到它,好比静态方法派发等等。app
接下来是 SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS
,这是一个宏定义,展开后即:ide
RefCounts<InlineRefCountBits> refCounts;
复制代码
这是一个至关重要东西,引用计数、弱引用、unowned 引用都与它有关,同时它也是 Swift 对象(文中后续的 Swift 对象均指引用类型,即 class
的实例)中较为复杂的一个结构。函数
其实说复杂也并非很复杂,咱们知道 Objective-C runtime 里就有不少 union 结构的应用,例如 isa_t
有 pointer 类型也有 nonpointer 类型,它们都占用了相同的内存空间,这样作的好处就是能更高效地使用内存,尤为是这些大量使用到的东西,能够大大减小运行期的开销。相似的技术在 JVM 里也有,就如对象头的 mark word。固然,Swift ABI 中也大量采用这种技术。工具
RefCounts
类型和 Side Table上面说到 RefCounts
类型,这里咱们就来看看它究竟是个什么东西。
先看一下定义:
template <typename RefCountBits>
class RefCounts {
std::atomic<RefCountBits> refCounts;
// ...
};
复制代码
这就是 RefCounts
的内存布局,我这里省略了全部的方法和类型定义。你能够把 RefCounts
想象成一个线程安全的 wrapper,模板参数 RefCountBits
指定了真实的内部类型,在 Swift ABI 里总共有两种:
typedef RefCounts<InlineRefCountBits> InlineRefCounts;
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;
复制代码
前者是用在 HeapObject
中的,然后者是用在 HeapObjectSideTableEntry
(Side Table)中的,这两种类型后文我会一一讲到。
通常来说,Swift 对象并不会用到 Side Table,一旦对象被 weak 或 unowned 引用,该对象就会分配一个 Side Table。
定义:
typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;
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;
// ...
};
复制代码
经过模板替换以后,InlineRefCountBits
实际上就是一个 uint64_t
,相关的一堆类型就是为了经过模板元编程让代码可读性更高(或者更低,哈哈哈)。
下面咱们来模拟一下对象引用计数 +1:
swift::swift_retain
:HeapObject *swift::swift_retain(HeapObject *object) {
return _swift_retain(object);
}
static HeapObject *_swift_retain_(HeapObject *object) {
SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
if (isValidPointerForNativeRetain(object))
object->refCounts.increment(1);
return object;
}
auto swift::_swift_retain = _swift_retain_;
复制代码
RefCounts
的 increment
方法:void increment(uint32_t inc = 1) {
// 3. 原子地读出 InlineRefCountBits 对象(即一个 uint64_t)。
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
RefCountBits newbits;
do {
newbits = oldbits;
// 4. 调用 InlineRefCountBits 的 incrementStrongExtraRefCount 方法
// 对这个 uint64_t 进行一系列运算。
bool fast = newbits.incrementStrongExtraRefCount(inc);
// 无 weak、unowned 引用时通常不会进入。
if (SWIFT_UNLIKELY(!fast)) {
if (oldbits.isImmortal())
return;
return incrementSlow(oldbits, inc);
}
// 5. 经过 CAS 将运算后的 uint64_t 设置回去。
} while (!refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_relaxed));
}
复制代码
到这里就完成了一次 retain 操做。
上面是不存在 weak、unowned 引用的状况,如今咱们来看看增长一个 weak 引用会怎样。
swift::swift_weakAssign
(暂时省略这块的逻辑,它属于引用者的逻辑,咱们如今先分析被引用者)RefCounts<InlineRefCountBits>::formWeakReference
增长一个弱引用:template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::formWeakReference()
{
// 分配一个 Side Table。
auto side = allocateSideTable(true);
if (side)
// 增长一个弱引用。
return side->incrementWeak();
else
return nullptr;
}
复制代码
重点来看一下 allocateSideTable
的实现:
template <>
HeapObjectSideTableEntry* RefCounts<InlineRefCountBits>::allocateSideTable(bool failIfDeiniting)
{
auto oldbits = refCounts.load(SWIFT_MEMORY_ORDER_CONSUME);
// 已有 Side Table 或正在析构就直接返回。
if (oldbits.hasSideTable()) {
return oldbits.getSideTable();
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
return nullptr;
}
// 分配 Side Table 对象。
HeapObjectSideTableEntry *side = new HeapObjectSideTableEntry(getHeapObject());
auto newbits = InlineRefCountBits(side);
do {
if (oldbits.hasSideTable()) {
// 此时可能其余线程建立了 Side Table,删除该线程分配的,而后返回。
auto result = oldbits.getSideTable();
delete side;
return result;
}
else if (failIfDeiniting && oldbits.getIsDeiniting()) {
return nullptr;
}
// 用当前的 InlineRefCountBits 初始化 Side Table。
side->initRefCounts(oldbits);
// 进行 CAS。
} while (! refCounts.compare_exchange_weak(oldbits, newbits,
std::memory_order_release,
std::memory_order_relaxed));
return side;
}
复制代码
还记得 HeapObject
里的 RefCounts
其实是 InlineRefCountBits
的一个 wrapper 吗?上面构造完 Side Table 之后,对象中的 InlineRefCountBits
就不是原来的引用计数了,而是一个指向 Side Table 的指针,然而因为它们实际都是 uint64_t
,所以须要一个方法来区分。区分的方法咱们能够来看 InlineRefCountBits
的构造函数:
LLVM_ATTRIBUTE_ALWAYS_INLINE
RefCountBitsT(HeapObjectSideTableEntry* side)
: bits((reinterpret_cast<BitsType>(side) >> Offsets::SideTableUnusedLowBits)
| (BitsType(1) << Offsets::UseSlowRCShift)
| (BitsType(1) << Offsets::SideTableMarkShift))
{
assert(refcountIsInline);
}
复制代码
其实仍是最多见的方法,把指针地址无用的位替换成标识位。
顺便,看一下 Side Table 的结构:
class HeapObjectSideTableEntry {
// FIXME: does object need to be atomic?
std::atomic<HeapObject*> object;
SideTableRefCounts refCounts;
public:
HeapObjectSideTableEntry(HeapObject *newObject)
: object(newObject), refCounts()
{ }
// ...
};
复制代码
此时再增长引用计数会怎样呢?来看下以前的 RefCounts::increment
方法:
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));
}
复制代码
template <typename RefCountBits>
void RefCounts<RefCountBits>::incrementSlow(RefCountBits oldbits,
uint32_t n) {
if (oldbits.isImmortal()) {
return;
}
else if (oldbits.hasSideTable()) {
auto side = oldbits.getSideTable();
// ---> 而后调用到这里。
side->incrementStrong(n);
}
else {
swift::swift_abortRetainOverflow();
}
}
复制代码
void HeapObjectSideTableEntry::incrementStrong(uint32_t inc) {
// 最终到这里,refCounts 是一个 RefCounts<SideTableRefCountBits> 对象。
refCounts.increment(inc);
}
复制代码
到这里咱们就须要引出 SideTableRefCountBits
了,它与前面的 InlineRefCountBits
很像,只不过又多了一个字段,看一下定义:
class SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>
{
uint32_t weakBits;
// ...
};
复制代码
不知道上面的内容你们看晕了没有,反正我一开始分析的时候费了点时间。
上面咱们讲了两种 RefCounts
,一种是 inline 的,用在 HeapObject
中,它实际上是一个 uint64_t
,能够当引用计数也能够当 Side Table 的指针。
Side Table 是一种类名为 HeapObjectSideTableEntry
的结构,里面也有 RefCounts
成员,是内部是 SideTableRefCountBits
,其实就是原来的 uint64_t
加上一个存储弱引用数的 uint32_t
。
WeakReference
上面说的都是被引用的对象所涉及的逻辑,而引用者这边的逻辑就稍微简单一些了,主要就是经过 WeakReference
这个类来实现的,比较简单,咱们简单过一下就行。
Swift 中的 weak
变量通过 silgen 以后都会变成 swift::swift_weakAssign
调用,而后派发给 WeakReference::nativeAssign
:
void nativeAssign(HeapObject *newObject) {
if (newObject) {
assert(objectUsesNativeSwiftReferenceCounting(newObject) &&
"weak assign native with non-native new object");
}
// 让被引用者构造 Side Table。
auto newSide =
newObject ? newObject->refCounts.formWeakReference() : nullptr;
auto newBits = WeakReferenceBits(newSide);
// 喜闻乐见的 CAS。
auto oldBits = nativeValue.load(std::memory_order_relaxed);
nativeValue.store(newBits, std::memory_order_relaxed);
assert(oldBits.isNativeOrNull() &&
"weak assign native with non-native old object");
// 销毁原来对象的弱引用。
destroyOldNativeBits(oldBits);
}
复制代码
弱引用的访问就更简单了:
HeapObject *nativeLoadStrongFromBits(WeakReferenceBits bits) {
auto side = bits.getNativeOrNull();
return side ? side->tryRetain() : nullptr;
}
复制代码
到这里你们发现一个问题没有,被引用对象释放了为何还能直接访问 Side Table?其实 Swift ABI 中 Side Table 的生命周期与对象是分离的,当强引用计数为 0 时,只有 HeapObject
被释放了。
只有全部的 weak
引用者都被释放了或相关变量被置 nil
后,Side Table 才能得以释放,相见:
void HeapObjectSideTableEntry::decrementWeak() {
// FIXME: assertions
// FIXME: optimize barriers
bool cleanup = refCounts.decrementWeakShouldCleanUp();
if (!cleanup)
return;
// Weak ref count is now zero. Delete the side table entry.
// FREED -> DEAD
assert(refCounts.getUnownedCount() == 0);
delete this;
}
复制代码
因此即使使用了弱引用,也不能保证相关内存所有被释放,由于只要 weak
变量不被显式置 nil
,Side Table 就会存在。而 ABI 中也有能够提高的地方,那就是若是访问弱引用变量时发现被引用对象已经释放,就将本身的弱引用销毁掉,避免以后重复无心义的 CAS 操做。固然 ABI 不作这个优化,咱们也能够在 Swift 代码里作。:)
以上就是 Swift 弱引用机制实现方式的一个简单的分析,可见思路与 Objective-C runtime 仍是很相似的,都采用与对象匹配的 Side Table 来维护引用计数。不一样的地方就是 Objective-C 对象在内存布局中没有 Side Table 指针,而是经过一个全局的 StripedMap
来维护对象和 Side Table 之间的关系,效率没有 Swift 这么高。另外 Objective-C runtime 在对象释放时会将全部的 __weak
变量都 zero-out,而 Swift 并无。
总的来讲,Swift 的实现方式会稍微简单一些(虽然代码更复杂,Swift 团队追求更高的抽象)。第一次分析 Swift ABI,本文仅供参考,若是存在错误,欢迎你们勘正。感谢!