Runtime學習 應用源碼學習 Runtime源碼分析,帶你瞭解OC實現過程。其中參考了大量的大神的代碼以及文獻,裡面也有個人的見解,歡迎拍磚,歡迎交流。 兩種常見使用場景 根據調試信息,發現兩者的區別是: 第一種進入到 第二種繞一個遠路,先初始化 兩者最終進入到如下方法 ...
Runtime學習 -- weak
應用源碼學習
Runtime源碼分析,帶你瞭解OC實現過程。其中參考了大量的大神的代碼以及文獻,裡面也有個人的見解,歡迎拍磚,歡迎交流。
兩種常見使用場景
/// 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(id location, 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.
return storeWeak
/
id objc_initWeak(id location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
(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_t weak_table, id referent_id,
id referrer_id, bool crashIfDeallocating)
{
/// 轉化為object
objc_object referent = (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
return referent_id;
// value not change.
}
```
先從這行數的參數說起,參數有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
中,同時更新相關計數器。
上面過程的流程如下:
消除弱引用
消除弱引用過程同註冊大致相同,只是部分地方是相反操作,不做贅述了