OC对象之旅 weak弱引用实现分析
Runtime学习 -- weak应用源码学习
[blockquote]
Runtime源码分析,带你了解OC实现过程。其中参考了大量的大神的代码以及文献,里面也有个人的见解,欢迎拍砖,欢迎交流。
[/blockquote]
两种常见使用场景
/// weak属性 @interface XX : XX @property(nonatomic,weak) Type* weakPtr; @end /// 代码块中使用 { /// 使用__weak __weak Type* weakPtr = [[SomeObject alloc] init]; }
根据调试信息,发现两者的区别是:
第一种进入到 id objc_storeWeak(id *location, id newObj)方法
``` /** This function stores a new value into a __weak variable. It would be used anywhere a __weak variable is the target of an assignment. @param location The address of the weak pointer itself @param newObj The new object this weak ptr should now point to @return \e newObj / id objc_storeWeak(idlocation, id newObj) { return storeWeak (location, (objc_object *)newObj); } ``` 第二种绕一个远路,先初始化 id objc_initWeak(id *location, id newObj)
``` Objective-C /** Initialize a fresh weak pointer to some object location. It would be used for code like: (The nil case) __weak id weakPtr; (The non-nil case) NSObject *o = ...; __weak id weakPtr = o; This function IS NOT thread-safe with respect to concurrent modifications to the weak variable. (Concurrent weak clear is safe.) @param location Address of __weak ptr.
@param newObj Object ptr. / id objc_initWeak(idlocation, id newObj) { if (!newObj) { *location = nil; return nil; }
return storeWeak (location, (objc_object*)newObj); } ```
两者最终进入到如下方法
template <HaveOld haveOld, HaveNew haveNew, CrashIfDeallocating crashIfDeallocating> static id storeWeak(id *location, objc_object *newObj) { ///略去,下面会进行分析 ... return (id)newObj; }
所以重点就在 storeWeak
这个方法中,let's do it
分析源码
storeWeak
源码的如下:
template <HaveOld haveOld, HaveNew haveNew, CrashIfDeallocating crashIfDeallocating> 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]; } else { oldTable = nil; } if (haveNew) { newTable = &SideTables()[newObj]; } else { newTable = nil; } SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable); if (haveOld && *location != oldObj) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); goto retry; } // Prevent a deadlock between the weak reference machinery // and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa. /// 注释大意是通过下面操作,保证所有的弱引用对象的isa都被初始化,这样可以防止死锁,PS,这里我不是太明白,求指教 if (haveNew && newObj) { /// 下面的操作是初始化isa Class cls = newObj->getIsa(); if (cls != previouslyInitializedClass && !((objc_class *)cls)->isInitialized()) { SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable); _class_initialize(_class_getNonMetaClass(cls, (id)newObj)); // If this class is finished with +initialize then we're good. // If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself) // then we may proceed but it will appear initializing and // not yet initialized to the check above. // Instead set previouslyInitializedClass to recognize it on retry. previouslyInitializedClass = cls; goto retry; } } // 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; }
- template 是C++的一种泛型实现,相当于这里申明了变量或者类型,可以在代码块中使用,用于处理不同的未知类型&枚举。
- haveOld 弱引用是否已经有所指向
- haveNew 是否有新的指向
- CrashIfDeallocating 执行方法时发生Deallocate是否Crash
PS:初始化ISA那部分为何能阻止死锁,我没有看懂 该函数流程如下:
重点来了:
/// SideTables oldTable = &SideTables()[oldObj]; newTable = &SideTables()[newObj]; /// taggedPointer是什么鬼 isTaggedPointer /// 注册弱引用 weak_register_no_lock(&newTable->weak_table, (id)newObj, location,crashIfDeallocating); /// 消除弱引用 weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
SideTable
SideTable
是一个结构体,定义如下
struct SideTable { spinlock_t slock; RefcountMap refcnts; weak_table_t weak_table; SideTable() { memset(&weak_table, 0, sizeof(weak_table)); } ~SideTable() { _objc_fatal("Do not delete SideTable."); } ///锁 .... };spinlock_t solck 锁 RefcountMap refcnts 强引用使用,略过
weak_table_t weak_table 弱引用表 SideTable
是存放引用关系的,对象通过Hash值操作,在SideTableBuf
中寻找与之对应的SideTable
,SideTableBuf
初始化过程如下:
alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)]; /// 会在Objc_init中调用该方法 static void SideTableInit() { /// 这句话貌似没什么卵用,求指教 new (SideTableBuf) StripedMap<SideTable>(); } /// 寻找SideTable static StripedMap<SideTable>& SideTables() { return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf); }
StripedMap
是一个泛型类,并重写了[]运算符,通过对象的地址,运算出Hash值,通过该hash值找到对象的SideTable ``` template class StripedMap { enum { CacheLineSize = 64 };
if TARGET_OS_EMBEDDED
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(p); /// 位运算可以控制返回值在0-63之间 return ((addr >> 4) ^ (addr >> 9)) % StripeCount; }
public: T& operator[] (const void *p) { return array[indexForPointer(p)].value; } /// 下面略去 ... }
### taggedPointer 简单的说,这是一种优化手段,即将对象的值,存入对象的地址中,这些工程师简直丧心病狂,就为了省一点内存嘛! ### 进入正题,看看怎么实现弱引用的 先看看注册的过程吧
/**
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_tweak_table, id referent_id, id referrer_id, bool crashIfDeallocating) { /// 转化为object objc_objectreferent = (objc_object *)referent_id; objc_object referrer = (objc_object)referrer_id; /// 如果是taggedPointer,就没有引用的过程了 if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable bool deallocating; if (!referent->ISA()->hasCustomRR()) { deallocating = referent->rootIsDeallocating(); } else { BOOL (allowsWeakReference)(objc_object, SEL) = (BOOL()(objc_object, SEL)) object_getMethodImplementation((id)referent, SEL_allowsWeakReference); if ((IMP)allowsWeakReference == _objc_msgForward) { return nil; } deallocating = ! (*allowsWeakReference)(referent, SEL_allowsWeakReference); } /// 如果正在被销毁 if (deallocating) { if (crashIfDeallocating) { _objc_fatal("Cannot form weak reference to instance (%p) of " "class %s. It is possible that this object was " "over-released, or is in the process of deallocation.", (void*)referent, object_getClassName((id)referent)); } else { return nil; } }
// now remember it and where it is being stored weak_entry_t *entry; if ((entry = weak_entry_for_referent(weak_table, referent))) { append_referrer(entry, referrer); } else { weak_entry_t new_entry(referent, referrer); weak_grow_maybe(weak_table); weak_entry_insert(weak_table, &new_entry); }
// Do not set *referrer. objc_storeWeak() requires that the // value not change.
return referent_id; } ``` 先从这行数的参数说起,参数有4个 weak_table_t *weak_table hash表 id referent_id, 弱引用对象 id *referrer_id, 弱引用指针
bool crashIfDeallocating 如果正在Deallocate是否crash
后三个参数不用解释,主要解释第一个参数,weak_table_t
,定义如下
/** * 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_table_t
就是寄存在SideTable
中
- weak_entry_t *weak_entries; ///数组,用于存储引用对象集合
- size_t num_entries; /// 存储数目
- uintptr_t mask; /// 当前分配容量
- uintptr_t max_hash_displacement; /// 已使用容量
定义中我们重点关注weak_entry_t
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& operator=(const weak_entry_t& other) { memcpy(this, &other, sizeof(other)); return *this; } 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_entry_t
是最终存放对象和引用指针的地方,referent
是被引用的对象,联合体union
释义如下
- weak_referrer_t *referrers; 存放引用指针
- uintptr_t out_of_line_ness : 2 标识当前存储是否在初始WEAK_INLINE_COUNT个数之内
- uintptr_t num_refs : PTR_MINUS_2 引用的个数
- uintptr_t mask; 实际分配容量
- uintptr_t max_hash_displacement; 实际使用容量,包括已经被释放的,每次调整容量时会更新重置
- weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; 当引用个数小于WEAK_INLINE_COUNT时,使用该数组存放。
注册引用过程中,重点关注下面代码:
{ weak_entry_t *entry; /// 查找是否已经注册过了 if ((entry = weak_entry_for_referent(weak_table, referent))) { /// 加上去就可以了 append_referrer(entry, referrer); } else { /// 新建一个 weak_entry_t new_entry(referent, referrer); /// 调整weak_table_t 的容量大小 weak_grow_maybe(weak_table); /// 插入一个 weak_entry_insert(weak_table, &new_entry); } }
新建
通过weak_entry_t
的源码,可以看到新建一个weak_entry_t
的过程是
- 将被引用对象赋予referent
- 将引用指针放入到inline_referrers,因为此时数目还很少
调整weak_table_t的容量大小
static void weak_resize(weak_table_t *weak_table, size_t new_size) { size_t old_size = TABLE_SIZE(weak_table); weak_entry_t *old_entries = weak_table->weak_entries; weak_entry_t *new_entries = (weak_entry_t *) calloc(new_size, sizeof(weak_entry_t)); weak_table->mask = new_size - 1; weak_table->weak_entries = new_entries; /// 重置 weak_table->max_hash_displacement = 0; weak_table->num_entries = 0; // restored by weak_entry_insert below if (old_entries) { weak_entry_t *entry; weak_entry_t *end = old_entries + old_size; for (entry = old_entries; entry < end; entry++) { if (entry->referent) { weak_entry_insert(weak_table, entry); } } free(old_entries); } } // Grow the given zone's table of weak references if it is full. static void weak_grow_maybe(weak_table_t *weak_table) { size_t old_size = TABLE_SIZE(weak_table); // Grow if at least 3/4 full. if (weak_table->num_entries >= old_size * 3 / 4) { weak_resize(weak_table, old_size ? old_size*2 : 64); } }
当实际的数目大于old_size(old_size就是mask的大小+1),就去调整大小,同时重置max_hash_displacement为0,通过calloc函数,动态分配mask个的内存,然后通过循环,将原有的weak_entry_t
插入到新的容器中,在插入的过程中,更新max_hash_displacement.
在weak_table_t插入weak_entry_t
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; /// 引用计数+1 weak_table->num_entries++; /// 扩容前最大占位 if (hash_displacement > weak_table->max_hash_displacement) { weak_table->max_hash_displacement = hash_displacement; } }
过程比较简单,也是利用hash处理,方便后面查找。
在weak_table_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; size_t begin = hash_pointer(referent) & weak_table->mask; /// 获取hash值 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_entry_t中加入引用
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. 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; 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_table_t
中插入weak_entry_t
如出一辙,要注意的是需要判断引用的个数,当引用个数大于WEAK_INLINE_COUNT时,需要将原有的引用指针也移到referrers
中,同时更新相关计数器。 上面过程的流程如下:
消除弱引用
消除弱引用过程同注册大致相同,只是部分地方是相反操作,不做赘述了