從@property提及(二)當咱們寫下@property (nonatomic, weak) id obj時,咱們究竟寫了什麼

@property (nonatomic, weak) id obj;

先看下面一段代碼編程

#import <Foundation/Foundation.h>

@interface TestClass : NSObject
@property (nonatomic, weak) id foo;
@end

@implementation TestClass
@synthesize foo = _foo;

- (id)foo {
    return _foo;
}

- (void)setFoo:(id)foo {
    _foo = foo;
}

@end

int main(int argc, const char * argv[]) {
    
    TestClass *obj = [TestClass new];
    obj.foo = [NSObject new];
    NSLog(@"%@", obj.foo);
    
    return 0;
}

這段代碼應該沒有疑問,輸出的是null。
那麼究竟爲何是null?若是你僅僅給出答案說,由於foo這個屬性是weak修飾,是否是太過簡單了點。
因此,問題要一點點分析,首先就是:函數

weak是如何作到不持有對象,但能獲得對象值的

這個問題,首先就是要先知道,weak真正的做用,實際是__weak修飾符。當咱們聲明一個weak屬性foo時,實際是聲明瞭一個__weak id _foo;
因而咱們真正要討論的,是__weak關鍵字。
實際上這個問題隨便搜一下有不少答案,大部分答案都來自於iOS業界著名的一本書——Kazuki Sakamoto所著《Objective-C高級編程》。這本書的確很好,可是,在有些地方我的認爲跳躍太大,致使讀者容易出現誤解。__weak關鍵字的實現部分就是如此。
首先咱們先看書中的解釋:atom

{
    id __weak obj1 = obj;
}

假設obj附加__strong修飾符並被賦值。
編譯器會把這段代碼轉換成運行時代碼指針

id obj1;  
objc_initWeak(&obj1, obj);  
objc_destoryWeak(&obj1);

即編譯器會經過objc_initWeak函數初始化__weak修飾的變量,當變量的做用域結束後會經過objc_destoryWeak函數釋放該變量。objc_initWeak函數實際乾的活是:code

objc1 = 0;  
objc_storeWeak(&obj1, obj);

這裏是先將指針objc1置成0,再調用objc_storeWeak函數使得obj1指向obj對象。
接下來的objc_destoryWeak函數的實際操做以下:對象

objc_storeWeak(&obj1, 0);

總結一下,剛纔的整個過程模擬代碼以下:作用域

id obj1;  
obj1 = 0;  
objc_storeWeak(&obj1, obj);  
objc_storeWeak(&obj1, 0);

實際上,objc_storeWeak函數會把第二個參數的對象的地址做爲key,並將第一個參數(__weak關鍵字修飾的指針的地址)做爲值,註冊到weak表中。若是第二個參數爲0(說明對應的對象被釋放了),則將weak表中將整個key-value鍵值對刪除,這就是__weak關鍵字的核心思想!
在那本書,到這裏講得都很是好,然而多是由於年代久遠,當時runtime也沒有開源,因此關於 id __weak obj = [NSObject new]這個問題描述不太清晰。
下面是個人例子:編譯器

int main(int argc, const char * argv[]) {
    NSObject *foo = [NSObject new];
    __weak id obj = foo;
    NSLog(@"%@", obj);
    obj = [NSObject new];
    NSLog(@"%@", obj);
    return 0;
}

顯然第一個log是有值的,第二個log應該是null。
那麼問題就是,爲何第二個log沒有辦法取到值。
固然這個結果自己就是__weak的特性,咱們要討論的,就是這個特性是如何實現的。源碼

首先,再給obj聲明並賦初值時,foo這個局部變量默認就是__strong的,因此,obj能夠儲存foo的值。這個沒有什麼疑問。那麼關鍵就在於,下面的obj
= [NSObject new]是不會調用objc_initWeak函數,可是依然成功把obj置空了。it

因此一步步分析:

obj = [NSObject new];

實際轉換爲

id objc_storeWeak(&obj, newObj)

newObj顯然不爲0,因而storeWeak函數成功返回了id類型的newObj。

而後關鍵在於下一步,參數newObj因爲沒有被任何變量引用,因此newObj的做用域在storeWeak函數返回後,就結束了。

因而

newObj調用下面的函數
void objc_release(id obj)
被成功釋放

而在此時,newObj的dealloc是調用了objc_clear_deallocating函數,這個函數實現能夠經過runtime源碼來查看,其中就包含:

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

這樣,就會調起obj的objc_destroyWeak(&obj)函數,而這個函數的實現是:

void
objc_destroyWeak(id *location)
{
    (void)storeWeak<true/*old*/, false/*new*/, false/*crash*/>
        (location, nil);
}

第二個參數直接傳nil,因而清理obj中的值。

因此,咱們再回頭看一下,@property (nonatomic, weak) id obj的setter方法:

- (void)setObj:(id)obj {
    _obj = obj;
}

實際上,setter的實現,在編譯完成後,會轉換爲

objc_storeWeak(&_obj, obj);
    objc_release(obj);

因此當咱們寫下

@property (nonatomic, weak) id obj;

咱們到底寫下了什麼。

在下一篇中,就須要講一下nonatomic和atomic了

相關文章
相關標籤/搜索