int main(int argc, const char * argv[]) {
NSObject *obj = [NSObject new];
__weak id p = obj;
return 0;
}
上面的代码,在编译成汇编的时候会添加两个函数:
callq _objc_initWeak
callq _objc_destroyWeak
可以在runtime的源码里找到对应的函数实现:
id
objc_initWeak(id *location, id newObj)
{
...
return storeWeak<DontHaveOld, DoHaveNew, DoCrashIfDeallocating>
(location, (objc_object*)newObj);
}
void
objc_destroyWeak(id *location)
{
(void)storeWeak<DoHaveOld, DontHaveNew, DontCrashIfDeallocating>
(location, nil);
}
先来分析下objc_initWeak里调用到的storeWeak。
SideTable
storeWeak函数里有这样一段代码:
newTable = &SideTables()[newObj];
这个函数里会先通过hash获取对象obj对应的SideTable,先来了解下SideTable。
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
SideTable里包含了弱引用表weak_table,SideTable存储在StripedMap的模板类里(可以把StripedMap看作一个哈希表)。StripedMap会以对象的地址作为key,经过哈希算法取出存储的SideTable。
//StripedMap是c++模板类,T是类型参数,这里把T看成SideTable类型
template<typename T>
class StripedMap {
enum { StripeCount = 64 };
struct PaddedT {
T value alignas(CacheLineSize);
};
//SideTable存储在这个数组里,数组长度是64
PaddedT array[StripeCount];
//哈希算法,把这个函数的返回值用作数组的下标,p是对象的地址,% StripeCount是为了避免数组越界
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;//% StripeCount避免数组越界
}
public:
//重写了[]操作符,可以通过StripedMap[对象地址]这样取出SideTable了。
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
};
从上面的代码里可以看出,StripedMap持有了一个长度是64的数组,数组里是SideTable
上面的哈希是有冲突的,indexForPointer()算出来的index值是0-63。多个对象可能会对应同一个SideTable。
下面看一下获取StripedMap的方法:
alignas(StripedMap<SideTable>) static uint8_t
SideTableBuf[sizeof(StripedMap<SideTable>)];
static void SideTableInit() {
new (SideTableBuf) StripedMap<SideTable>();
}
static StripedMap<SideTable>& SideTables() {
return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
}
上面的代码不是特别明白,StripedMap是从SideTables()函数里获取的。SideTables()函数里大概是把静态数组SideTableBuf强转成了StripedMap<SideTable>类型。
storeWeak()
objc_initweak会调用到storeweak函数,storeweak函数里会先获取对象对应的sideTable,然后调用到weak_register_no_lock注册弱引用指针。
weak_table_t
之前已经获取到了obj对应的sidetable了,sidetable里就可以取到weak_table_t对象。
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
weak_entries是一个数组,里面包含了weak_entry_t的结构体,一个对象对应一个weak_entry_t,在取出weak_entry_t,也用到哈希,对象obj作为哈希的key(把weak_table_t看作一个哈希表)。
//从weak_entries数组中通过hash取出weak_entry_t
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;
//referent是对象的地址,以对象的地址为key作hash算出来的值作为数组的下标
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];
}
static inline uintptr_t hash_pointer(objc_object *key) {
return ptr_hash((uintptr_t)key);
}
static inline uint32_t ptr_hash(uint64_t key)
{
key ^= key >> 4;
key *= 0x8a970be7488fda55;
key ^= __builtin_bswap64(key);
return (uint32_t)key;
}
weak_entry_t
上面提到的方法通过obj哈希计算得到了对应的weak_entry_t对象,我们要存储的 p对象的的弱引用指针的地址是存储在weak_entry_t里的。
接下来看一下弱引用对象p的地址是怎么存出的。
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];
};
};
bool out_of_line() {
return (out_of_line_ness == REFERRERS_OUT_OF_LINE);
}
};
weak_entry_t里包含两个数组:
#define WEAK_INLINE_COUNT 4
weak_referrer_t *referrers;
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
这两个数组里存储的是弱引用指针的地址。
如果对象的弱引用指针不超过四个,将弱引用指针的地址存储在inline_referrers里的,否则存储到referrers里。
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) {
// Try to insert inline.
//将弱引用指针的地址存储到inline_referrers里
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.
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);
}
//这里又用到了hash,这里是用对象的弱引用指针的地址为key的,不是对象的地址,算出来的hash值作为数组的下标使用。
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
//解决哈希冲突
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;
}
//将弱引用对象的指针new_referrer放入数组referrers下标index的位置,weak_referrer_t &是c++引用的写法
weak_referrer_t &ref = entry->referrers[index];
ref = new_referrer;
entry->num_refs++;
}
上面的代码是先判断inline_referrers能否存储,如果已经存满了就使用referrers。使用referrers的时候有用到了hash,用弱引用指针的地址new_referrer作为key算出的值作为index,存到了数组里entry->referrers[index] = new_referrer
。到这里就完成了弱引用指针的注册。
总结一下,注册weak的过程先用对象的地址为key,通过hash得到了对象对应的SideTable,然后再用对象的地址为key,通过hash从SideTable.weak_table里取出来weak_entry_t,然后会把弱引用指针的地址存储到weak_entry_t.inline_referrers里,如果weak_entry_t.inline_referrers已经存不下了,就用弱引用指针的地址做一个hash存储到了weak_entry_t.referrers中。
weak_unregister_no_lock
objc_destroyWeak会调用到storeweak,然后调用到weak_unregister_no_lock,然后remove_referrer():
static void remove_referrer(weak_entry_t *entry, objc_object **old_referrer)
{
if (! entry->out_of_line()) {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}
}
size_t begin = w_hash_pointer(old_referrer) & (entry->mask);
size_t index = begin;
size_t hash_displacement = 0;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
if (index == begin) bad_weak_table(entry);
hash_displacement++;
if (hash_displacement > entry->max_hash_displacement) {
objc_weak_error();
return;
}
}
entry->referrers[index] = nil;
entry->num_refs--;
}
首先也是先通过对象地址hash找到对象对应的weak_entry_t,然后会先从weak_entry_t. inline_referrers里遍历,如果找到了弱引用指针的地址,就移除弱引用指针的地址entry->inline_referrers[i] = nil;
,否则就从weak_entry_t. referrers里通过hash去找,找到就移除entry->referrers[index] = nil; entry->num_refs--;
,这个和上面讲到的注册弱应用是对应的。
int main(int argc, const char * argv[]) {
NSObject *obj = [NSObject new];
__weak id p = obj;
return 0;
}
上面这种写法objc_destroyWeak是发生在obj对象调用dealloc之前的。也就是obj对象dealloc之前弱引用指针的地址已经移除了,并不会出现常说的对象释放的时候去把弱引用指针置nil。
__weak id pp = nil;
int main(int argc, const char * argv[]) {
NSObject *obj = [NSObject new];
__weak id pp = obj;
return 0;
}
这种在汇编的时候会添加_objc_storeWeak
,没有_objc_destroyWeak
。
当对象调用obj的dealloc方法时,会调用到clearDeallocating_slow,最后会调用到weak_clear_no_lock。
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
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;
}
//过遍历数组,将弱应用指针*referrer 置nil
for (size_t i = 0; i < count; ++i) {
objc_object **referrer = referrers[i];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry);
}
这里面也是新通过hash找到了weak_referrer_t,然后不管是weak_referrer_t .inline_referrers还是weak_referrer_t.referrers,最后都是通过遍历数组的方式,把所有的弱引用指针置成nil了。这就是在对象释放的时候,从哈希表里取出弱引用指针置nil。
总结:
int main(int argc, const char * argv[]) {
NSObject *obj = [NSObject new];
__weak id p = obj;
return 0;
}
如上:
1.使用__weak修饰的对象p,首先会通过obj哈希得到对应的sidetable对象,获取到sidetable对象中的weak_table_t对象;
2.然后再通过obj哈希从weak_table_t中获取到对应的weak_entry_t对象;3.weak_entry_t对象中包含了一个长度是4的静态数组,弱引用对象p的地址会优先存储在静态数组里,超出长度限制后会用到weak_entry_t对象的动态数组,动态数组存储弱引用对象p的地址是通过弱引用对象p的地址哈希算出index作为数组下标存储的;
4.当对象obj释放时,通过上面的过程找到weak_entry_t的数组并通过遍历数组的方式将所有存储的弱引用对象置位nil。