【iOS】——SideTable

SideTable

Side Table主要用于存储和管理对象的额外信息,特别是与弱引用相关的数据。Side Table的设计和使用是Objective-C运行时实现弱引用的基础,使得ARC(Automatic Reference Counting)能够正确地处理弱引用的生命周期。

新版本的 objc 中引入了 Tagged Pointer,且 isa 采用 union 的方式进行构造,其中 isa 的结构体中有一个 extra_rchas_sidetable_rc,这两者共同记录引用计数器。

在进行retain操作时,会调用rootRetain方法

objc_object::rootRetain() 方法,只看 extra_rc 超出之后 sidetable 相关的代码,删减之后如下:

objective-c 复制代码
uintptr_t carry;
    newisa.bits = addc(newisa.bits, RC_ONE, 0, &carry);  // extra_rc++

if (carry) {
    // Leave half of the retain counts inline and prepare to copy the other half to the side table.
    transcribeToSideTable = true;
    newisa.extra_rc = RC_HALF;
    newisa.has_sidetable_rc = true;
}
if (slowpath(transcribeToSideTable)) {
        // Copy the other half of the retain counts to the side table.
        sidetable_addExtraRC_nolock(RC_HALF);
}

这里主要就是调用了 sidetable_addExtraRC_nolock()

objective-c 复制代码
bool 
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
    assert(isa.nonpointer);
    // 取出this对象所在的SideTable
    SideTable& table = SideTables()[this];
    // 取出SideTable中存储的refcnts,类型为Map
    size_t& refcntStorage = table.refcnts[this];
    // 记录原始的引用计数器
    size_t oldRefcnt = refcntStorage;

    // 容错处理
    assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
    assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
    if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;

    uintptr_t carry;
    size_t newRefcnt = 
        addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
    if (carry) {
        // SideTable溢出处理
        refcntStorage =
            SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
        return true;
    } else {
        // SideTable未溢出
        refcntStorage = newRefcnt;
        return false;
    }
}
  1. 首先根据 this,也就是对象的地址从 SideTables 中取出一个 SideTable;
  2. 接着获取 SideTable 的 refcnts,这个成员变量是一个 Map;
  3. 然后存储旧的引用计数器;
  4. 然后进行 add 计算,并记录是否有溢出;
  5. 最后根据是否溢出计算并记录结果,最后返回;

SideTables定义

objective-c 复制代码
static StripedMap<SideTable>& SideTables() {
    return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}

SideTablesde的实质类型是存储SideTable的StripedMap 。在StripedMap类中有StripeCount 定义存储sidetable的最大数量,所以每个SideTablesdes可以对应多个对象,而每个对象对应一个sidetable

SideTableBuf

objective-c 复制代码
// We cannot use a C++ static initializer to initialize SideTables because
// libc calls us before our C++ initializers run. We also don't want a global 
// pointer to this struct because of the extra indirection.
// Do it the hard way.

alignas(StripedMap<SideTable>) static uint8_t 
    SideTableBuf[sizeof(StripedMap<SideTable>)];
  • SideTables 在 C++ 的 initializers 函数之前被调用,所以不能使用 C++ 初始化函数来初始化 SideTables,而 SideTables 本质就是 SideTableBuf;

  • 不能使用全局指针来指向这个结构体,因为涉及到重定向问题;

SideTableBuf 本质上是一个长度为sizeof(StripedMap) 的 char 类型的数组;同时也可以这么理解:

SideTableBuf 本质上就是一个大小为和 StripedMap<SideTable> 对象一致的内存块;

这也是为什么 SideTableBuf 可以用来表示 StripedMap<SideTable> 对象。本质上而言,SideTableBuf 就是指一个 StripedMap<SideTable>对象;

StripedMap < SideTable >

StripedMap 是一个模板类,在这个类中有一个 array 成员,用来存储 PaddedT 对象,并且其中对于 [] 符的重载定义中,会返回这个 PaddedT 的 value 成员,这个 value 就是我们传入的 T 泛型成员,也就是 SideTable 对象。在 array 的下标中,这里使用了 indexForPointer 方法通过位运算计算下标,实现了静态的 Hash Table。而在 weak_table 中,其成员 weak_entry 会将传入对象的地址加以封装起来,并且其中也有访问全局弱引用表的入口。

objective-c 复制代码
template<typename T>
class StripedMap {
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
    enum { StripeCount = 8 };
#else
    enum { StripeCount = 64 };
#endif

    struct PaddedT {
        T value alignas(CacheLineSize);
    };

    PaddedT array[StripeCount];

    static unsigned int indexForPointer(const void *p) {
        uintptr_t addr = reinterpret_cast<uintptr_t>(p);
        return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

 public:
    T& operator[] (const void *p) { 
        return array[indexForPointer(p)].value; 
    }
    const T& operator[] (const void *p) const { 
        return const_cast<StripedMap<T>>(this)[p]; 
    }
    ...省略了对象方法...
}
  • 首先根据是否为 iphone 定义了一个 StripeCount,iphone 下为 8;

  • 源码中 CacheLineSize 为 64,使用 T 定义了一个结构体,而 T 就是 SideTable 类型;

  • 生成了一个长度为 8 类型为 SideTable 的数组;

  • indexForPointer() 逻辑为根据传入的指针,经过一定的算法,计算出一个存储该指针的位置,因为使用了取模运算,所以值的范围是 0 ~ (StripeCount-1),所以不会出现数组越界;

  • 后面的 operator 表示重写了运算符 [] 的逻辑,调用了 indexForPointer() 方法,这样使用起来更像一个数组;

SideTables 可以理解成一个类型为 StripedMap< SideTable> 静态全局对象,内部以数组的形式存储了 StripeCount 个 SideTable;

SideTable
objective-c 复制代码
struct SideTable {
// 保证原子操作的自旋锁    
		spinlock_t slock;
// 引用计数的 hash 表
    RefcountMap refcnts;
// weak 引用全局 hash 表
    weak_table_t weak_table;
    //构造函数
    SideTable() {
        memset(&weak_table, 0, sizeof(weak_table));
    }
		//析构函数
    ~SideTable() {
        _objc_fatal("Do not delete SideTable.");
    }
    ...省略对象方法...
}

slock是一个自旋锁,就是为了保证多线程访问安全性

refcnts本质是一个存储对象引用计数的hash表,key为对象,value为引用计数(优化过得isa中,引用计数主要存储在isa中)

weak_table是存储对象弱引用的一个结构体,该结构体内成员如下:

objective-c 复制代码
/**
   全局的弱引用表, 保存object作为key, weak_entry_t作为value
 * The global weak references table. Stores object ids as keys,
 * and weak_entry_t structs as their values.
 */
struct weak_table_t {
    // 保存了所有指向特地对象的 weak指针集合
    weak_entry_t *weak_entries;
    // weak_table_t中有多少个weak_entry_t
    size_t    num_entries;
    // weak_entry_t数组的count
    uintptr_t mask;
    // hash key 最大偏移值,
    // 采用了开放定制法解决hash冲突,超过max_hash_displacement说明weak_table_t中不存在要找的weak_entry_t
    uintptr_t max_hash_displacement;
};

下面是refcnts的定义:

objective-c 复制代码
typedef objc::DenseMap<DisguisedPtr<objc_object>,size_t,RefcountMapValuePurgeable> RefcountMap;

DenseMap 是一个 hash Map,基类 DenseMapBase 中的部分代码,如下,DenseMapBase中重写了操作符 []:

objective-c 复制代码
ValueT &operator[](const KeyT &Key) {
    return FindAndConstruct(Key).second;
  }

通过传入的 Key 寻找对应的 Value。而 Key 是 DisguisedPtr<objc_object> 类型,Value 是 size_t 类型。即使用 obj.address :refCount 的形式来记录引用计数器;

回到最初的 sidetable_addExtraRC_nolock 方法中:

objective-c 复制代码
size_t& refcntStorage = table.refcnts[this];

通过 this ,即 object 对象的地址,取出 refcnts 这个哈希表中存储的引用计数器;

refcnts 可以理解成一个 Map,使用 address:refcount 的形式存储了很多个对象的引用计数器;

总结

  • iphone 中 SideTables() 本质是返回一个 SideTableBuf 对象,该对象存储 8 个 SideTable;

  • 因为涉及到多线程和效率的问题,必定不可能只使用一个 SideTable 来存储对象相关的引用计数器和弱引用;

  • Apple 通过对 object 的地址进行运算之后,对 SideTable 的个数进行取模运算,以此来决定将对象分配到哪个 SideTable 进行信息存储,因为有取模运算,不会出现数组溢出的情况;

  • 当对象需要使用到 sideTable 时,会被分配到 8/64 个全局 sideTables 中的某一个表中存储相关的引用计数器或者弱引用信息;

相关推荐
幽夜落雨1 分钟前
ios老版本应用安装方法
ios
gxhlh6 小时前
局域网中 Windows 与 Mac 互相远程连接的最佳方案
windows·macos
宏基骑士6 小时前
mac 电脑上安装adb命令
macos·adb
胖虎18 小时前
实现 iOS 自定义高斯模糊文字效果的 UILabel(文末有Demo)
ios·高斯模糊文字·模糊文字
水银嘻嘻13 小时前
【Mac】Python相关知识经验
开发语言·python·macos
梦魇梦狸º1 天前
mac 配置 python 环境变量
chrome·python·macos
丁总学Java1 天前
macOS如何进入 Application Support 目录(cd: string not in pwd: Application)
macos
qdprobot1 天前
Mixly米思齐1.0 2.0 3.0 软件windows版本MAC苹果电脑系统安装使用常见问题与解决
windows·macos
麦克Mapp1 天前
不用安装双系统,如何在mac上玩windows游戏呢?
macos
符小易1 天前
Mac苹果电脑 怎么用word文档和Excel表格?
macos·word·excel