1. 程式人生 > >Weak關鍵字介紹

Weak關鍵字介紹

interface 關鍵字 property import 先來

由淺入深

先來看看最簡單的一個例子:

#import "ViewController.h"@interface ViewController ()@property (nonatomic,strong)id strongPoint;@property (nonatomic,weak)id weakPoint;@[email protected] ViewController- (void)viewDidLoad {
    [super viewDidLoad];//    self.strongPoint = [NSDate date];
    self.strongPoint = [[UILabel alloc] init];    self.weakPoint = self.strongPoint;    self.strongPoint = nil;    NSLog(@"result is :%@", self.weakPoint);
[email protected]
/* */

我們可以看到此時輸出的結果為:

2017-02-07 13:20:41.119278 Test[7341:2187477] result is :(null)

如果我們使用的strong來修飾weakPoint,此時輸出的結果為:

2017-02-07 13:23:13.211164 Test[7344:2187993] result is :<UILabel: 0x100206070; frame = (0 0; 0 0); userInteractionEnabled = NO; layer = <_UILabelLayer: 0x17009a590>>

如果我們使用assign來修飾weakPoint,此時運行程序可能會崩潰(因為如果引用操作發生時內存還沒有改變內容,依舊可以輸出正確結果,如果引用的時候內存內容發生改變了,就會crash),因為當assign指針所指向的內存被釋放之後,不會自動賦值為nil,這樣再次引用該指針的時候就會導致野指針操作。

對上述代碼運行結果進行分析:
當使用weak關鍵字的時候,不會增加對象的計數,而且當所指對象置nil的時候,使用weak修飾的指針將被賦值為nil;
當使用strong關鍵字的時候,會增加對象的計數,也就是說會保持對象值的存在,所以當使用strong的時候weakPoint還會有值。
因此,我們從這裏可以得出一個結果:
strong是強引用,它會保持對象值的存在;
weak是弱引用,當weak指針指向的對象摧毀之後,屬性值也會清空(nil out)。
(註意:使用 _ _ weak修飾 和在@ property裏面設置weak是一樣的)
但是當我們執行如下代碼的時候:

__strong NSString *yourString = @"Your String"; 
__weak NSString *myString = yourString;
yourString = nil;
__unsafe_unretained NSString *theirString = myString;NSLog(@"%p %@", yourString, yourString);NSLog(@"%p %@", myString, myString);NSLog(@"%p %@", theirString, theirString);

你會發現只有yourString為空,其他兩個都不為空,這個是為什麽呢?原因如下


這裏是因為字符字面值永遠不會被釋放,所以你的weak指針還是指向它。
當你使用@""創建一個string對象的時候,它就是一個字面值,永遠不會被改變。如果你在程序中很多地方都用到了一樣的字符串,那麽你可以測試一下,它們都是同一個對象(地址一樣),String字面值不會銷毀。使用[[NSString alloc] initWithString:@"literal string"]也是一樣的效果。因為它指向了一個字面值的string。


那麽請問weak指針指向對象被回收的時候該指針是如何被自動置為nil的呢??


首先,大家可以看一下博客最後面的附錄,裏面有兩個文檔,嚴格來說是Apple的opensouce。裏面有一個objc-weak的類。這裏是一個objc-weak.h類和一個objc-weak.mm類。

擴展常識
.m和.mm的區別
.m:源代碼文件,這個典型的源代碼文件擴展名,可以包含OC和C代碼。
.mm:源代碼文件,帶有這種擴展名的源代碼文件,除了可以包含OC和C代碼之外,還可以包含C++代碼。僅在你的OC代碼中確實需要使用C++類或者特性的時候才用這種擴展名。

從.h中可以看到以下幾個關鍵的兩個結構體:weak_entry_t和weak_table_t,以及一些方法。接下來簡單介紹一下weak如何自動置為nil。
weak的實現其實是一個weak表,該表是一個由自旋鎖管理的哈希表。
以下是從NSObject.mm裏面摘出的一些方法:

idobjc_initWeak(id *location, id newObj)
{    if (!newObj) {
        *location = nil;        return nil;
    }    return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
        (location, (objc_object*)newObj);
}

該function的作用是初始化一個新的weak指針指向對象的地址。其中的參數介紹如下:

  • location段:_ _ weak指針的地址

  • newObj:對象的指針地址

這裏調用的storeWeak方法,storeWeak方法裏面通過template模板的參數進行更新weak操作,看源碼可以知道裏面會調用weak_register_no_lock/weak_unregister_no_lock等objc-weak.mm裏面的方法進行相應的操作。objc-weak.h裏面有句話:

For ARR, we also keep track of whether an arbitrary object is being 
deallocated by briefly placing it in the table just prior to invoking 
dealloc, and removing it via objc_clear_deallocating just prior to memory 
reclamation.

對象被廢棄時最後調用objc_clear_deallocating。該函數實現如下:

void objc_clear_deallocating(id obj) {    assert(obj);    assert(!UseGC);    if (obj->isTaggedPointer()) return;
    obj->clearDeallocating();
}

也就是調用了clearDeallocating,繼續追蹤可以發現,它最終是使用了叠代器來取weak表的value,然後調用weak_clear_no_lock,然後查找對應的value,將該weak指針置空:

/**  * 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) 
{
    objc_object *referent = (objc_object *)referent_id;

    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;
    }    
    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);
}

objc_clear_deallocating該函數的動作如下:

  1. 從weak表中獲取廢棄對象的地址為鍵值的記錄

  2. 將包含在記錄中的所有附有 _ _ weak修飾符變量的地址,賦值為nil

  3. 將weak表中刪除該記錄

  4. 從引用計數表中刪除廢棄對象的地址為鍵值的記錄
    看了objc-weak.mm的源碼大概了解了:其實Weak表示一個hash表,然後裏面的key是指向對象的地址,Value是Weak指針的地址的數組


Weak關鍵字介紹