關於weak的知識我就不再多說,直接開始我們的原始碼分析之旅
__weak
id __week obj1 = obj;複製程式碼
編譯器的模擬程式碼
id obj1;
obj1 = 0;
objc_storeWeak(&obj1, obj);
objc_storeWeak(&obj1, 0);複製程式碼
objc_storeWeak
函式把第二引數的賦值物件的地址作為鍵值,將第一引數的附有__weak修飾的變數的地址註冊到weak表中。
如果第二引數為0,則把變數的地址從weak表中刪除。
initWeak的實現
id objc_initWeak(id *object, id value)
{
*object = 0;
return objc_storeWeak(object, value);
}複製程式碼
storeWeak是Objective-C的開源部分
讓我們來看看storeWeak到底是怎麼實現的
objc_storeWeak
storeWeak的原始碼
官方英文註釋挺全的,可以直接理解~
// 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.
}複製程式碼
來看看storeWeak的實現
獲取oldObj/newObj
if (HaveOld) {
oldObj = *location;
oldTable = &SideTables()[oldObj];
} else {
oldTable = nil;
}
if (HaveNew) {
newTable = &SideTables()[newObj];
} else {
newTable = nil;
}複製程式碼
首先是根據weak指標找到其指向的老的物件:
oldObj = *location;複製程式碼
然後獲取到與新舊物件相關的SideTable物件:
oldTable = &SideTables()[oldObj];
newTable = &SideTables()[newObj];複製程式碼
&SideTables()[oldObj]這是什麼鬼??
其時是 實現了一個類 StripedMap 過載了[]操作符
(c++: 哪裡都能看到我 233)
public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}複製程式碼
下面要做的就是在老物件的weak表中移除指向資訊,而在新物件的weak表中建立關聯資訊:
if (HaveOld) {
weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if (HaveNew) {
newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
// weak_register_no_lock returns NULL if weak store should be rejected
}複製程式碼
接下來讓弱引用指標指向新的物件:
*location = newObj;複製程式碼
最後會返回這個新物件:
return newObj;複製程式碼
以上我們能發現weak的管理實際上跟weak_table有這千絲萬縷的聯絡,接下來就對weak_table進行分析!
weakTable
(關先上原始碼還是先總結...我思考了很久...。。。。)
- weak表是一個弱引用表,實現為一個weak_table_t結構體,儲存了所有物件相關的的所有的弱引用資訊
- 其中weak_entry_t是儲存在弱引用表中的一個內部結構體,它負責維護和儲存指向一個物件的所有弱引用hash表。
- weak_entry_t中的referrers 儲存了指向weak物件的所有變數
來張圖直觀感受一下:
下面開始對這些結構體進行分析:
- SideTable是一個用C++實現的類,它的具體定義在NSObject.mm中
class SideTable { private: static uint8_t table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE]; public: RefcountMap refcnts;//引用計數表 weak_table_t weak_table;//弱引用表 ...... }複製程式碼
weak表的結構定義:
/**
* 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;
};複製程式碼
根據註釋我們可以得到這是一張全域性的儲存object的id 和 keys的表.
weak_entry_t 作為他們的值.
來看weak_entry_t的結構體
struct weak_entry_t {
DisguisedPtr<objc_object> referent;
union {
struct {
weak_referrer_t *referrers;
uintptr_t out_of_line : 1;
uintptr_t num_refs : PTR_MINUS_1;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
struct {
// out_of_line=0 is LSB of one of these (don't care which)
weak_referrer_t inline_referrers[WEAK_INLINE_COUNT];
};
};
};複製程式碼
referrers: 是指向weak物件的所有變數
referent: 是記憶體上的weak物件
現在我們可以得出什麼結論了呢
- OC中 弱引用變數的管理是利用 weak表(Hash表)來管理的
- weak表中的weak_entries負責管理指向weak物件的變數
weak物件的釋放
釋放物件時,廢棄誰都不持有的物件的同時,程式的動作是怎麼樣的呢?
- objc_release
- 因為引用計數為0所以執行dealloc
- objc rootDealloc
- objc dispose
- objc destructInstance
- objc clear dealloctaing
物件被廢棄時最後呼叫的objc_clear_deallocating函式的動作如下:
(1) 從weak表中獲取廢棄物件的地址為鍵值的記錄。
(2) 將包含在記錄中的所有附有__weak修飾符變數的地址,賦值為nil
(3) 從weak表中刪除該記錄。
(4) 從引用計數表中刪除廢棄物件的地址為鍵值的記錄。
由此可知,如果大量使用附有weak修飾符的變數,則會消耗相應的CPU資源。良策是隻在需要避免迴圈引用時使用weak修飾符。
立即釋放物件
{
id __weak obj = [[NSObject alloc] init];
}複製程式碼
因為該原始碼將自己生成並持有的物件賦值給附有__weak修飾符的變數中,所以自己不能持有該物件,這是會被釋放並且廢棄。
使用附有__weak修飾符的變數,即是使用註冊到autoreleasepool中的物件
{
id __weak obj1 = obj;
NSLog(@"%@",obj1);
}複製程式碼
編譯器模擬的程式碼
id obj1;
objc_initWeak(&obj,obj);
id tmp = objc_loadWeakRetained(&obj);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
objc_destoryWeak(&obj1);複製程式碼
注意這兩行程式碼
id tmp = objc_loadWeakRetained(&obj);
objc_autorelease(tmp);複製程式碼
與賦值時相比,在使用附有__weak修飾符變數的情況下,增加了對objc_loadWeakRetained函式和objc_autorelease函式的呼叫。
(1) objc_loadWeakRetained 函式取出附有__weak修飾符變數所引用的物件並retain
(2) objc_autorelease 函式將物件註冊到autoreleasepool中。
注意:
每次使用weak修飾的變數,會使變數所引用的物件註冊到autoreleasepool中。
如果要避免這種情況可以將附有weak修飾符的變數賦值給附有__strong修飾符的變數後再次使用。
id __weak o = obj;
id tmp = o;複製程式碼
allowWeakReference/retainWeakReference
當allowsWeakReference/retainWeakReference例項方法(沒有寫入NSObject介面說明文件中)返回NO的情況。
- (BOOL)allowsWeakReference;
- (BOOL)retainWeakReference;複製程式碼
在賦值給__weak修飾符的變數時,如果allowsWeakReference方法返回NO,程式將異常終止。
物件retain時,如果retainWeakReference方法返回NO, 該變數將使用nil
具體原始碼分析
以上關於weak的
- weak_register_no_lock
- weak_unregister_no_lock
- 。。。
很多具體實現都沒有講...
我把自己看的程式碼加上註釋貼出來了...感興趣的可以看一下具體的實現...感受原始碼實現的魅力
怎麼理解呢objc_object **referrer?
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;複製程式碼
我們要結合remove_referrer這個函式來理解
remove_referrer
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i] == old_referrer) {
entry->inline_referrers[i] = nil;
return;
}
}複製程式碼
所以我們要拿到referrer 根據這個值來和entry連結串列中的指標進行比較,如果發現,就nil
原始碼中你可能有疑惑的TaggedPoint:
TaggedPoint知識
(指標搞得我都暈了...佩服c/c++系統工程師)
(閱讀原始碼真的是一件有意思的是哈哈)
#include "objc-private.h"
#include "objc-weak.h"
#include <stdint.h>
#include <stdbool.h>
#include <sys/types.h>
#include <libkern/OSAtomic.h>
#define TABLE_SIZE(entry) (entry->mask ? entry->mask + 1 : 0)
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer);
BREAKPOINT_FUNCTION(
void objc_weak_error(void)
);
/**
* Unique hash function for object pointers only.
*
* @param key The object pointer
*
* @return Size unrestricted hash of pointer.
*/
static inline uintptr_t hash_pointer(objc_object *key) {
return ptr_hash((uintptr_t)key);
}
/**
* Unique hash function for weak object pointers only.
*
* @param key The weak object pointer.
*
* @return Size unrestricted hash of pointer.
*/
static inline uintptr_t w_hash_pointer(objc_object **key) {
return ptr_hash((uintptr_t)key);
}
/**
* Grow the entry's hash table of referrers. Rehashes each
* of the referrers.
*
* @param entry Weak pointer hash set for a particular object.
*/
__attribute__((noinline, used))
static void grow_refs_and_insert(weak_entry_t *entry,
objc_object **new_referrer)
{
assert(entry->out_of_line);
size_t old_size = TABLE_SIZE(entry);
size_t new_size = old_size ? old_size * 2 : 8;
size_t num_refs = entry->num_refs;
weak_referrer_t *old_refs = entry->referrers;
entry->mask = new_size - 1;
entry->referrers = (weak_referrer_t *)
calloc(TABLE_SIZE(entry), sizeof(weak_referrer_t));
entry->num_refs = 0;
entry->max_hash_displacement = 0;
for (size_t i = 0; i < old_size && num_refs > 0; i++) {
if (old_refs[i] != nil) {
append_referrer(entry, old_refs[i]);
num_refs--;
}
}
// Insert
append_referrer(entry, new_referrer);
if (old_refs) free(old_refs);
}
/**
* 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 is Array implementation
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 = 1;
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);
}
//find a place to insert ref
//weak_entry_remove() may bzero() some place
size_t index = w_hash_pointer(new_referrer) & (entry->mask);
size_t hash_displacement = 0;
while (entry->referrers[index] != NULL) {
index = (index+1) & entry->mask;
hash_displacement++;
}
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++;
}
/**
* Remove old_referrer from set of referrers, if it's present.
* Does not remove duplicates, because duplicates should not exist.
*
* @todo this is slow if old_referrer is not present. Is this ever the case?
*
* @param entry The entry holding the referrers.
* @param old_referrer The referrer to remove.
*/
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;
}
}
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
size_t index = w_hash_pointer(old_referrer) & (entry->mask);
size_t hash_displacement = 0;
while (entry->referrers[index] != old_referrer) {
index = (index+1) & entry->mask;
hash_displacement++;
if (hash_displacement > entry->max_hash_displacement) {
_objc_inform("Attempted to unregister unknown __weak variable "
"at %p. This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
old_referrer);
objc_weak_error();
return;
}
}
entry->referrers[index] = nil;
entry->num_refs--;
}
/**
* 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);
//mask may keep entry in array
size_t index = hash_pointer(new_entry->referent) & (weak_table->mask);
size_t hash_displacement = 0;
//hash index 處理
while (weak_entries[index].referent != nil) {
index = (index+1) & weak_table->mask;
hash_displacement++;
}
weak_entries[index] = *new_entry;
weak_table->num_entries++;
//update max_hash_displacement
if (hash_displacement > weak_table->max_hash_displacement) {
weak_table->max_hash_displacement = hash_displacement;
}
}
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;
//new
weak_table->weak_entries = new_entries;
weak_table->max_hash_displacement = 0;
weak_table->num_entries = 0; // restored by weak_entry_insert below
//use pointer
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);
}
}
// Shrink the table if it is mostly empty.
static void weak_compact_maybe(weak_table_t *weak_table)
{
size_t old_size = TABLE_SIZE(weak_table);
// Shrink if larger than 1024 buckets and at most 1/16 full.
if (old_size >= 1024 && old_size / 16 >= weak_table->num_entries) {
weak_resize(weak_table, old_size / 8);
// leaves new table no more than 1/2 full
}
}
/**
* Remove entry from the zone's table of weak references.
*/
static void weak_entry_remove(weak_table_t *weak_table, weak_entry_t *entry)
{
// remove entry
if (entry->out_of_line) free(entry->referrers);
//bzero()函式在由s指向的區域中放置n個0。
bzero(entry, sizeof(*entry));
weak_table->num_entries--;
//maybe resize weak_table
weak_compact_maybe(weak_table);
}
/**
* 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 index = hash_pointer(referent) & weak_table->mask;
size_t hash_displacement = 0;
while (weak_table->weak_entries[index].referent != referent) {
index = (index+1) & weak_table->mask;
hash_displacement++;
if (hash_displacement > weak_table->max_hash_displacement) {
return nil;
}
}
return &weak_table->weak_entries[index];
}
/**
* Unregister an already-registered weak reference.
* This is used when referrer's storage is about to go away, but referent
* isn't dead yet. (Otherwise, zeroing referrer later would be a
* bad memory access.)
* Does nothing if referent/referrer is not a currently active weak reference.
* Does not zero referrer.
*
* FIXME currently requires old referent value to be passed in (lame)
* FIXME unregistration should be automatic if referrer is collected
*
* @param weak_table The global weak table.
* @param referent The object.
* @param referrer The weak reference.
*/
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) {
remove_referrer(entry, referrer);
bool empty = true;
//after unregister the entry's referrers is empty?
// Hash implementation
if (entry->out_of_line && entry->num_refs != 0) {
empty = false;
}
// Array implementation
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
// if entry.references empty
if (empty) {
weak_entry_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
/**
* 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;
//The Point which point the object
objc_object **referrer = (objc_object **)referrer_id;
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
// judge is Allows Weak Reference
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;
new_entry.referent = referent;
new_entry.out_of_line = 0;
new_entry.inline_referrers[0] = referrer;
for (size_t i = 1; i < WEAK_INLINE_COUNT; i++) {
new_entry.inline_referrers[i] = nil;
}
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;
}
#if DEBUG
bool
weak_is_registered_no_lock(weak_table_t *weak_table, id referent_id)
{
return weak_entry_for_referent(weak_table, (objc_object *)referent_id);
}
#endif
/**
* 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)
{
//referent objc
objc_object *referent = (objc_object *)referent_id;
//referent objc entry(which save many referents)
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;
}
//entry->referrers all 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_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);
}
/**
* This function gets called when the value of a weak pointer is being
* used in an expression. Called by objc_loadWeakRetained() which is
* ultimately called by objc_loadWeak(). The objective is to assert that
* there is in fact a weak pointer(s) entry for this particular object being
* stored in the weak-table, and to retain that object so it is not deallocated
* during the weak pointer's usage.
*
* @param weak_table
* @param referrer The weak pointer address.
*/
/*
Once upon a time we eagerly cleared *referrer if we saw the referent
was deallocating. This confuses code like NSPointerFunctions which
tries to pre-flight the raw storage and assumes if the storage is
zero then the weak system is done interfering. That is false: the
weak system is still going to check and clear the storage later.
This can cause objc_weak_error complaints and crashes.
So we now don't touch the storage until deallocation completes.
*/
id
weak_read_no_lock(weak_table_t *weak_table, id *referrer_id)
{
objc_object **referrer = (objc_object **)referrer_id;
objc_object *referent = *referrer;
//Detection Tagged Pointer
if (referent->isTaggedPointer()) return (id)referent;
weak_entry_t *entry;
// referent == nil or entry == nil
if (referent == nil ||
!(entry = weak_entry_for_referent(weak_table, referent)))
{
return nil;
}
//Custom RR denotes a custom retain-release implementation
//
if (! referent->ISA()->hasCustomRR()) {
//???question
if (! referent->rootTryRetain()) {
return nil;
}
}
//has isa
else {
BOOL (*tryRetain)(objc_object *, SEL) = (BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
SEL_retainWeakReference);
//IMP != _objc_magForward
if ((IMP)tryRetain == _objc_msgForward) {
return nil;
}
//IMP != nil
if (! (*tryRetain)(referent, SEL_retainWeakReference)) {
return nil;
}
}
return (id)referent;
}複製程式碼
「掘金技術徵文」
juejin.im/post/58d8e9…