散列表SideTables是内存管理的方案之一,是重要表之一。
包含了引用计数表和弱引用表。
在源码map_images_nolock中,我们之前都是直接定位到最后的read_images方法,这个方法用来加载类。在加载类之上,找到下面代码。
...
if (firstTime) {
//这个方法会初始化方法表,并注册用到的内置方法,如load,initialize,retain,autorelease等。
sel_init(selrefCount);
arr_init();
}
.....
void arr_init(void)
{
AutoreleasePoolPage::init();
SideTableInit(); // 初始化SideTable
}
static void SideTableInit() {
new (SideTableBuf) StripedMap<SideTable>();
}
在使用时,调用的是SideTables。如下:
table = &SideTables()[obj];
//获取全局静态SideTables
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
看下StripedMap的结构。
enum { CacheLineSize = 64 };
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];
}
.....
};
通过上面代码看出,SideTables是一个全局hash数组,里面每一个元素是一个SideTable。SideTables的数组长度,来自StripedMap中的StripeCount。
查看SideTable的结构。
struct SideTable {
spinlock_t slock;
RefcountMap refcnts; //引用计数表
weak_table_t weak_table; //弱引用表
.....
};
其中SideTable中又包含了引用计数表和弱引用计数表。所以,会存在多个对象共用一个SideTable。
-
如何找到对象所在的
SideTable?通过
indexForPointer方法,作hash算法,找到所在的SideTable。 -
为什么不直接用一张
SideTable,而是用SideTables去管理多个SideTable?SideTable里有一个自旋锁,如果把所有的类都放在同一个SideTable,有任何一个类有改动都会对整个table做操作,并且在操作一个类的同时,操作别的类会被锁住等待,这样会导致操作效率和查询效率都很低。而有多个SideTable的话,操作的都是单个Table,并不会影响其他的table,这就是分离锁。
全局的引用计数之所以不存在同一张表中,是为了避免资源竞争,解决效率的问题。引用计数表 中引入了 分离锁的概念,将一张表分拆成多个部分,对他们分别加锁,可以实现并发操作,提升执行效率
-
如何找到对象的引用计数?
先找到所在的
SideTable,然后搜索SideTable中的refcnts。refcnts也是一张hash表,key为对象的地址,value是引用计数值。
根据上文我们知道,一个SideTable中存储了多个对象的信息,信息分为引用计数和弱引用关系。
接下来,我们看下⚠️⚠️重要的weak_table。
首先明确,一个weak_table中存储了多个对象的弱引用信息。看下结构。
/**
* The global weak references table. Stores object ids as keys,
* and weak_entry_t structs as their values.
*/
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
weak_entries是一个动态数组,对象通过hash算法,找到weak_entry_t。即指向对象的弱引用信息。看下结构。
#define WEAK_INLINE_COUNT 4
struct weak_entry_t {
DisguisedPtr<objc_object> referent; //【对象】
union {
// 动态数组模式
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line_ness : 2;
uintptr_t num_refs : PTR_MINUS_2;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
// 定长数组模式
struct {
// out_of_line_ness field is low bits of inline_referrers[1]
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
weak_entry_t(objc_object *newReferent, objc_object **newReferrer)
: referent(newReferent)
{
inline_referrers[0] = newReferrer;//【这里是**,所以存放的是指针的地址】
for (int i = 1; i < WEAK_INLINE_COUNT; i++) {
inline_referrers[i] = nil;
}
}
..........
};
当弱引用指针个数少于等于WEAK_INLINE_COUNT时,会使用定长数组inline_referrers。而当大于WEAK_INLINE_COUNT时,则会转换到动态数组模式weak_referrer_t*referrers。这个地方在调用的时候会进行判断。
初始化一个weak的对象
Person *p = [[Person alloc] init];
__weak Person *p1 = p;
p = 0x100f3c6f0
在第二行代码时,进入到runtime层。
id
objc_initWeak(id *location, id newObj)
{
//【这里location用id*,newObj用id,说明location=指针的地址,后面的函数中,使用到的location都是指针的地址】
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
此时
(id *) location = 0x7ffeefbff4e0,即p1的地址
newObj = (Person *) 0x100f3c6f0
进入到storeWeak
static id
storeWeak(id *location, objc_object *newObj)
{
assert(haveOld || haveNew);
if (!haveNew) assert(newObj == nil);
Class previouslyInitializedClass = nil;
id oldObj;
SideTable *oldTable;
SideTable *newTable;
// Acquire locks for old and new values.
// Order by lock address to prevent lock ordering problems.
// Retry if the old value changes underneath us.
retry:
if (haveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj]; //【根据weak指针找到其指向的老的对象】
} else {
oldTable = nil; //【走到这里,此时haveOld=false】
}
if (haveNew) {
newTable = &SideTables()[newObj]; //【通过hash找到newObj所在的SideTable】
} else {
newTable = nil;
}
SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);
.....//省略
// Clean up old value, if any.
if (haveOld) {
//【清空旧表】
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
// Assign new value, if any.
if (haveNew) {
//【注册】
newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating);
// weak_register_no_lock returns nil if weak store should be rejected
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj; // 【让指针指向对象,这个时候是*】
}
else {
// No new value. The storage is not changed.
}
SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);
return (id)newObj;
}
查看weak_register_no_lock。
/**
* Registers a new (object, weak pointer) pair. Creates a new weak
* object entry if it does not exist.
*
* @param weak_table The global weak table.
* @param referent The object pointed to by the weak reference.
* @param referrer The weak pointer address.
*/
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
//【(Person *) referent_id = 0x0000000100f3c6f0】
objc_object *referent = (objc_object *)referent_id;
//【(id *) referrer_id = 0x00007ffeefbff4e0】
objc_object **referrer = (objc_object **)referrer_id;
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
.......
// now remember it and where it is being stored
weak_entry_t *entry;
//【先查看是否已经存在以对象为key的弱引用关系表】
if ((entry = weak_entry_for_referent(weak_table, referent))) {
//【存在,直接追加】
append_referrer(entry, referrer);
}
else {
//【不存在当前对象地址为key的弱引用表】
//【通过构造方法,初始化并插入referrer在第一个】
weak_entry_t new_entry(referent, referrer);
weak_grow_maybe(weak_table);//【是否超过3/4,如果是,扩容,*2,初始值为64】
//【将新的new_entry加入到weak_table中】
weak_entry_insert(weak_table, &new_entry);
}
// Do not set *referrer. objc_storeWeak() requires that the
// value not change.
return referent_id;
}
至此,初始化操作结束。我们再来看下=nil的场景。
weak的释放
⚠️⚠️重点来了,常问的面试题当weak引用指向的对象被释放时,又是如何去处理weak指针的呢?
Person *p = [[Person alloc] init];
__weak Person *p1 = p; //【初始化操作】
p = nil; //【对象释放】
当释放对象时,其基本流程如下图:

weak_clear_no_lock。
/**
* Called by dealloc; nils out all weak pointers that point to the
* provided object so that they can no longer be used.
*
* @param weak_table
* @param referent The object being deallocated.
*/
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
//类型转换
objc_object *referent = (objc_object *)referent_id;
//找到weak_entry_t
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
weak_referrer_t *referrers;
size_t count;
//【取出定长数组或动态数组】
if (entry->out_of_line()) {
referrers = entry->referrers;
count = TABLE_SIZE(entry);
}
else {
referrers = entry->inline_referrers;
count = WEAK_INLINE_COUNT;
}
//遍历查找,清空
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry); //移除entry
}
这个函数首先是找出对象对应的weak_entry_t链表,然后将弱引用置为nil。最后清理对象的记录。如图所示。

一个weak引用的处理涉及各种查表、添加与删除操作,还是有一定消耗的。所以如果大量使用__weak变量的话,会对性能造成一定的影响。那么,我们应该在什么时候去使用weak呢?《Objective-C高级编程》给我们的建议是只在避免循环引用的时候使用__weak修饰符。
weak_entry_for_referent
/**
* Return the weak reference table entry for the given referent.
* If there is no entry for referent, return NULL.
* Performs a lookup.
*
* @param weak_table
* @param referent The object. Must not be nil.
*
* @return The table of weak referrers to this object.
*/
static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{
assert(referent);
weak_entry_t *weak_entries = weak_table->weak_entries;
if (!weak_entries) return nil;
size_t begin = hash_pointer(referent) & weak_table->mask;
size_t index = begin;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_table->weak_entries);
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
遍历weak_table->weak_entries,通过hash算法,计算index,取出weak_entry_t,判断referent是否相等,如果相等,说明存在以当前对象地址为key的弱引用表,返回weak_entry_t。
append_referrer
/**
* Add the given referrer to set of weak pointers in this entry.
* Does not perform duplicate checking (b/c weak pointers are never
* added to a set twice).
*
* @param entry The entry holding the set of weak pointers.
* @param new_referrer The new weak pointer to be added.
*/
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
//【没有使用动态数组时】
if (! entry->out_of_line()) {
// Try to insert inline.
//【记录,这里是**,所以是 指针的地址】
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == nil) {
entry->inline_referrers[i] = new_referrer;
return; //【如果定长满足,直接返回】
}
}
// Couldn't insert inline. Allocate out of line.
//【不能插入到inline_referrers,空间已满,分配动态数组,转为referrers】
weak_referrer_t *new_referrers = (weak_referrer_t *)
calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));
// This constructed table is invalid, but grow_refs_and_insert
// will fix it and rehash it.
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
new_referrers[i] = entry->inline_referrers[i];
}
entry->referrers = new_referrers;
entry->num_refs = WEAK_INLINE_COUNT;
entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;
entry->mask = WEAK_INLINE_COUNT-1;
entry->max_hash_displacement = 0;
}
//【下面是动态数组添加数据,此时 一定是动态数组,如果定长满足,走不到这里】
assert(entry->out_of_line());
if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {
//【扩容操作+追加操作】
return grow_refs_and_insert(entry, new_referrer);
}
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
//【hash找到空位置】
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement;
}
//【找到空位置,存放】
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
weak_entry_insert
/**
* Add new_entry to the object's table of weak references.
* Does not check whether the referent is already in the table.
*/
static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{
weak_entry_t *weak_entries = weak_table->weak_entries;
assert(weak_entries != nil);
size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
if (index == begin) bad_weak_table(weak_entries);
hash_displacement++;
}
weak_entries[index] = *new_entry;
weak_table->num_entries++;
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}