[轉]weak的生命週期:具體實現方法

weak的生命週期:具體實現方法

2015-06-05 09:03 編輯: lansekuangtu 分類:iOS開發 來源:南峯子的技術博客
3447

1.jpg

咱們都知道weak表示的是一個弱引用,這個引用不會增長對象的引用計數,而且在所指向的對象被釋放以後,weak指針會被設置的爲nil。weak引用一般是用於處理循環引用的問題,如代理及block的使用中,相對會較多的使用到weak。html

以前對weak的實現略有了解,知道它的一個基本的生命週期,但具體是怎麼實現的,瞭解得不是太清晰。今天又翻了翻《Objective-C高級編程》關於__weak的講解,在此作個筆記。ios

咱們如下面這行代碼爲例:git

代碼清單1:示例代碼github

1
2
3
{
     id __weak obj1 = obj;
}

當咱們初始化一個weak變量時,runtime會調用objc_initWeak函數。這個函數在Clang中的聲明以下:編程

1
id objc_initWeak(id *object, id value);

其具體實現以下:數組

1
2
3
4
5
id objc_initWeak(id *object, id value)
{
     *object = 0;
     return  objc_storeWeak(object, value);
}

示例代碼輪換成編譯器的模擬代碼以下:app

1
2
id obj1;
objc_initWeak(&obj1, obj);

所以,這裏所作的事是先將obj1初始化爲0(nil),而後將obj1的地址及obj做爲參數傳遞給objc_storeWeak函數。ide

objc_initWeak函數有一個前提條件:就是object必須是一個沒有被註冊爲__weak對象的有效指針。而value則能夠是null,或者指向一個有效的對象。函數

若是value是一個空指針或者其指向的對象已經被釋放了,則object是zero-initialized的。不然,object將被註冊爲一個指向value的__weak對象。而這事應該是objc_storeWeak函數乾的。objc_storeWeak的函數聲明以下:

1
id objc_storeWeak(id *location, id value);

其具體實現以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
id objc_storeWeak(id *location, id newObj)
{
     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:
     oldObj = *location;
     oldTable = SideTable::tableForPointer(oldObj);
     newTable = SideTable::tableForPointer(newObj);
     ......
     if  (*location != oldObj) {
         OSSpinLockUnlock(lock1);
#if SIDE_TABLE_STRIPE > 1
         if  (lock1 != lock2) OSSpinLockUnlock(lock2);
#endif
         goto retry;
     }
     if  (oldObj) {
         weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
     }
     if  (newObj) {
         newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
         // weak_register_no_lock returns NULL if weak store should be rejected
     }
     // Do not set *location anywhere else. That would introduce a race.
     *location = newObj;
     ......
     return  newObj;
}

咱們撇開源碼中各類鎖操做,來看看這段代碼都作了些什麼。在此以前,咱們先來了解下weak表和SideTable。

weak表是一個弱引用表,實現爲一個weak_table_t結構體,存儲了某個對象相關的的全部的弱引用信息。其定義以下(具體定義在objc-weak.h中):

1
2
3
4
5
struct weak_table_t {
     weak_entry_t *weak_entries;
     size_t    num_entries;
     ......
};

其中weak_entry_t是存儲在弱引用表中的一個內部結構體,它負責維護和存儲指向一個對象的全部弱引用hash表。其定義以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
struct weak_entry_t {
     DisguisedPtr referent;
     union {
         struct {
             weak_referrer_t *referrers;
             uintptr_t        out_of_line : 1;
             ......
         };
         struct {
             // out_of_line=0 is LSB of one of these (don't care which)
             weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
         };
     };
};

其中referent是被引用的對象,即示例代碼中的obj對象。下面的union即存儲了全部指向該對象的弱引用。由註釋能夠看到,當out_of_line等於0時,hash表被一個數組所代替。另外,全部的弱引用對象的地址都是存儲在weak_referrer_t指針的地址中。其定義以下:

1
typedef objc_object ** weak_referrer_t;

SideTable是一個用C++實現的類,它的具體定義在NSObject.mm中,咱們來看看它的一些成員變量的定義:

1
2
3
4
5
6
7
8
class SideTable {
private:
     static uint8_t table_buf[SIDE_TABLE_STRIPE * SIDE_TABLE_SIZE];
public:
     RefcountMap refcnts;
     weak_table_t weak_table;
     ......
}

RefcountMap refcnts,你們應該能猜到這個作什麼用的吧?看着像是引用計數什麼的。哈哈,貌似就是啊,這東東存儲了一個對象的引用計數的信息。固然,咱們在這裏不去探究它,咱們關注的是weak_table。這個成員變量指向的就是一個對象的weak表。

瞭解了weak表和SideTable,讓咱們再回過頭來看看objc_storeWeak。首先是根據weak指針找到其指向的老的對象:

1
oldObj = *location;

而後獲取到與新舊對象相關的SideTable對象:

1
2
oldTable = SideTable::tableForPointer(oldObj);
newTable = SideTable::tableForPointer(newObj);

下面要作的就是在老對象的weak表中移除指向信息,而在新對象的weak表中創建關聯信息:

1
2
3
4
5
6
7
if  (oldObj) {
     weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
}
if  (newObj) {
     newObj = weak_register_no_lock(&newTable->weak_table, newObj,location);
     // weak_register_no_lock returns NULL if weak store should be rejected
}

接下來讓弱引用指針指向新的對象:

1
*location = newObj;

最後會返回這個新對象:

1
return  newObj;

objc_storeWeak的基本實現就是這樣。固然,在objc_initWeak中調用objc_storeWeak時,老對象是空的,全部不會執行weak_unregister_no_lock操做。

而當weak引用指向的對象被釋放時,又是如何去處理weak指針的呢?當釋放對象時,其基本流程以下:

  1. 調用objc_release

  2. 由於對象的引用計數爲0,因此執行dealloc

  3. 在dealloc中,調用了_objc_rootDealloc函數

  4. 在_objc_rootDealloc中,調用了object_dispose函數

  5. 調用objc_destructInstance

  6. 最後調用objc_clear_deallocating

咱們重點關注一下最後一步,objc_clear_deallocating的具體實現以下:

1
2
3
4
5
6
7
8
9
10
11
12
13
void objc_clear_deallocating(id obj) 
{
     ......
     SideTable *table = SideTable::tableForPointer(obj);
     // clear any weak table items
     // clear extra retain count and deallocating bit
     // (fixme warn or abort if extra retain count == 0 ?)
     OSSpinLockLock(&table->slock);
     if  (seen_weak_refs) {
         arr_clear_deallocating(&table->weak_table, obj);
     }
     ......
}

咱們能夠看到,在這個函數中,首先取出對象對應的SideTable實例,若是這個對象有關聯的弱引用,則調用arr_clear_deallocating來清除對象的弱引用信息。咱們來看看arr_clear_deallocating具體實現:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
PRIVATE_EXTERN void arr_clear_deallocating(weak_table_t *weak_table, id referent) {
     {
         weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
         if  (entry == NULL) {
             ......
             return ;
         }
         // zero out references
         for  (int i = 0; i < entry->referrers.num_allocated; ++i) {
             id *referrer = entry->referrers.refs[i].referrer;
             if  (referrer) {
                 if  (*referrer == referent) {
                     *referrer = nil;
                 }
                 else  if  (*referrer) {
                     _objc_inform( "__weak variable @ %p holds %p instead of %p\n" , referrer, *referrer, referent);
                 }
             }
         }
         weak_entry_remove_no_lock(weak_table, entry);
         weak_table->num_weak_refs--;
     }
}

這個函數首先是找出對象對應的weak_entry_t鏈表,而後挨個將弱引用置爲nil。最後清理對象的記錄。

經過上面的描述,咱們基本能瞭解一個weak引用從生到死的過程。從這個流程能夠看出,一個weak引用的處理涉及各類查表、添加與刪除操做,仍是有必定消耗的。因此若是大量使用__weak變量的話,會對性能形成必定的影響。那麼,咱們應該在何時去使用weak呢?《Objective-C高級編程》給咱們的建議是隻在避免循環引用的時候使用__weak修飾符。

另外,在clang中,還提供了很多關於weak引用的處理函數。如objc_loadWeak, objc_destroyWeak, objc_moveWeak等,咱們能夠在蘋果的開源代碼中找到相關的實現。等有時間,我再好好研究研究。

參考

  1. 《Objective-C高級編程》1.4: __weak修飾符

  2. Clang 3.7 documentation – Objective-C Automatic Reference Counting (ARC)

  3. apple opensource – NSObject.mm

轉載http://www.cocoachina.com/ios/20150605/11990.html

相關文章
相關標籤/搜索