温故而知新 - ObjC Swift 引用计数的实现对比

536 阅读3分钟

摘要

ObjC 引用计数,以哈希表形式,存在于全局的几个 SideTable 之中。

而 Swift 则是对象自行保存着引用计数的关系。

ObjC

引用计数表数据结构:obj 作为 key,计数作为 value。

以 RefcountMap 的结构,存储在 SideTable 中,而后者则是使用 StripedMap 进行管理。

(关于 StripedMap 可查看 Objc StripedMap 优化加锁缓存

相关源码如下。

SideTable 中的 refcnts 即为引用计数。

struct SideTable {
    spinlock_t slock; // 自旋锁
    RefcountMap refcnts; // 引用计数
    weak_table_t weak_table;
    ...
}

而 RefcountMap 实际就是 DenseMapBase。

// key 是 DisguisedPtr<objc_object>, value 是 size_t
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,true> RefcountMap;

// DenseMap 的父类
class DenseMapBase {
protected:
    typedef std::pair<KeyT, ValueT> BucketT;
    ...
}

我们可以将 DenseMapBase 简单地理解为「哈希表」即可。

当然它远不只这么简单,更多内容可看文末链接。

SideTable_retain()

引用计数表是如何更新的?

SideTable_retain() 入手,可看到先获取全局的 SideTables(),然后从中获取引用计数,再增加。


static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

id
objc_object::SideTable_retain()
{
#if SUPPORT_NONPOINTER_ISA
    assert(!isa.nonpointer);
#endif
    SideTable& table = SideTables()[this];

    table.lock();
    size_t& refcntStorage = table.refcnts[this];
    if (! (refcntStorage & SIDE_TABLE_RC_PINNED)) {
        refcntStorage += SIDE_TABLE_RC_ONE;
    }
    table.unlock();

    return (id)this;
}

其调用堆栈:

picture 1

而触发 objc_object::retain 有以下场景

20210328-1156.png

Swift

与 ObjC 不同,Swift 中的对象 (HeapObject) 直接保存着引用关系。

swift/stdlib/public/SwiftShims/RefCount.h 的注释,简明概要地说明了引用计数是如何保存的。

/*
  Strong and unowned variables point at the object.
  Weak variables point at the object's side table.


  Storage layout:

  HeapObject {
    isa
    InlineRefCounts {
      atomic<InlineRefCountBits> {
        strong RC + unowned RC + flags
        OR
        HeapObjectSideTableEntry*
      }
    }
  }

  HeapObjectSideTableEntry {
    SideTableRefCounts {
      object pointer
      atomic<SideTableRefCountBits> {
        strong RC + unowned RC + weak RC + flags
      }
    }
  }
*/

来看看相关源码。

struct HeapObject {
  /// This is always a valid pointer to a metadata object.
  HeapMetadata const *metadata;

  // typedef swift::RefCounts<swift::InlineRefCountBits> swift::InlineRefCounts
  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

typedef RefCounts<InlineRefCountBits> InlineRefCounts;
typedef RefCounts<SideTableRefCountBits> SideTableRefCounts;

从以上代码,可以看到 InlineRefCountBitsSideTableRefCountBits 其实都是 RefCounts

两者的声明:

typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;

class alignas(sizeof(void*) * 2) SideTableRefCountBits : public RefCountBitsT<RefCountNotInline>;

而以下的注释,再次强调了引用计数的存储位置。

// RefCountIsInline: refcount stored in an object
// RefCountNotInline: refcount stored in an object's side table entry
enum RefCountInlinedness {RefCountNotInline = false, RefCountIsInline = true};

swift_retain()

同样地,来看看 Swift 是如何更新引用计数的。

入口函数:

static HeapObject *_swift_retain_(HeapObject *object) {
  SWIFT_RT_TRACK_INVOCATION(object, swift_retain);
  if (isValidPointerForNativeRetain(object))
    object->refCounts.increment(1);
  return object;
}

增加引用计数的方法。

template <typename RefCountBits>
class RefCounts {
    // Increment the reference count.
    void increment(uint32_t inc = 1) {
        ...

        RefCountBits newbits;
        do {
            newbits = oldbits;
            bool fast = newbits.incrementStrongExtraRefCount(inc);

            // 无 weak、unowned 引用时一般不会进入。
            if (SWIFT_UNLIKELY(!fast)) {
                if (oldbits.isImmortal(false))
                return;
                return incrementSlow(oldbits, inc);
            }
        } while (!refCounts.compare_exchange_weak(oldbits, newbits,
                                                std::memory_order_relaxed));
    }

一般的增加引用计数:

// 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) {
    // This deliberately overflows into the UseSlowRC field.
    bits += BitsType(inc) << Offsets::StrongExtraRefCountShift;
    return (SignedBitsType(bits) >= 0);
}

使用 HeapObjectSideTableEntry 的情况:

class RefCounts {
    // 当有 weak、unowned 引用时,才会调用
    template <typename RefCountBits>
    void RefCounts<RefCountBits>::incrementSlow(RefCountBits oldbits,
                                                uint32_t n) {
        if (oldbits.isImmortal(false)) {
            return;
        }
        else if (oldbits.hasSideTable()) {
            // Out-of-line slow path.
            auto side = oldbits.getSideTable();
            // 调用的是 void HeapObjectSideTableEntry::incrementStrong(uint32_t inc) 方法
            side->incrementStrong(n);
        }
        else {
            // Retain count overflow.
            swift::swift_abortRetainOverflow();
        }
    }
}

// void HeapObjectSideTableEntry::incrementStrong(uint32_t inc)
void incrementStrong(uint32_t inc) {
  refCounts.increment(inc);
}

小结

ObjC 引用计数关系,以哈希表形式,存在于全局的几个 SideTable 之中。

Swift 则是对象自身保存着引用计数的关系。

比较特殊的是,当被 weak 或 unowned 引用时,对象会被分配一个 SideTable,后者用于保存引用关系。

相比之下,Swift 的方案,因为没有哈希计算,增删改查的效率都更高些。

感谢

iOS-Runtime 随笔——DenseMap 浅析 | 崔岚清的个人博客

从源码解析 Swift 弱引用 - 知乎