先看下面一段代碼編程
#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屬性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了