MRC 環境
一、靜態變數 和 全域性變數 在加和不加 __block 都會直接引用變數地址。也就意味著 可以修改變數的值。在沒有加__block 引數的情況下。
- 全域性block 和 棧block 區別為 是否引用了外部變數,堆block 則是對棧block copy 得來。對全域性block copy 不會有任何作用,返回的依然是全域性block。
二, 常量變數(NSString *a = @"hello";a 為常量變數,@“hello”為常量。)-----不加__block型別 block 會引用常量的地址(淺拷貝)。加__block型別 block會去引用常量變數(如:a變數,a = @"abc".可以任意修改a 指向的內容。)的地址。
如果不加__block 直接在block 內部修改變數 ,會編譯報錯。block 內部改變數是 只讀的。
但是 就一定可以推斷 block 會深拷貝 該變數嗎???
對於常量 @“hello” 儲存在 記憶體中的常量區, 程式結束才會釋放 記憶體。 如:
NSString *str = @"hello";
NSString *abcStr = @"hello";
編譯器會優化處理, str 和 abcStr 都會指向 常量區的@“hello” 地址。
NSString *str = @"hello"; void (^print)(void) = ^{ NSLog(@"block=str======%p",str); } str = @"hello1"; print();
block 會拷貝變數內容到自己的棧記憶體上,以便執行時可以呼叫。 但並不是對str 內容做了深拷貝,重新申請記憶體。
因為str 是棧記憶體上的變數,指向 一個常量區的@“hello”. 編譯器做的優化是 當block 去拷貝str 指向內容時發現是個常量,
所以會去引用 @“hello” 的指標,沒必要再取申請一塊記憶體。
三、物件變數 如(MyClass *class、Block block)。 這裡block 也是”類“物件(類似物件,其包含isa指標,clang 反編譯可以檢視。因為它不像從NSObject 繼承下來的物件都支援 retain、copy、release)。
下面直接引用文章裡總結的,經驗證無誤。
Block的copy、retain、release操作
不同於NSObjec的copy、retain、release操作:
- Block_copy與copy等效,Block_release與release等效;
- 對Block不管是retain、copy、release都不會改變引用計數retainCount,retainCount始終是1;
- NSGlobalBlock:retain、copy、release操作都無效;
- NSStackBlock:retain、release操作無效,必須注意的是,NSStackBlock在函式返回後,Block記憶體將被回收。即使retain也沒用。容易犯的錯誤是[
[mutableAarry addObject:stackBlock]
,在函式出棧後,從mutableAarry中取到的stackBlock已經被回收,變成了野指標。正確的做法是先將stackBlock copy到堆上,然後加入陣列:[mutableAarry addObject:[[stackBlock copy] autorelease]]
。支援copy,copy之後生成新的NSMallocBlock型別物件。 - NSMallocBlock支援retain、release,雖然retainCount始終是1,但記憶體管理器中仍然會增加、減少計數。copy之後不會生成新的物件,只是增加了一次引用,類似retain;
- 儘量不要對Block使用retain操作。
文章連結:http://tanqisen.github.io/blog/2013/04/19/gcd-block-cycle-retain/
以下補充
棧block : 猜測》》》》會copy 內部引用的物件變數。(如何驗證 block copy 了外部變數......在block 執行前 釋放物件.)。
但實際對兩種物件變數的操作為:
- MyClass *class : 棧block 並不會copy 物件變數,也不會retain 物件。而是直接引用了物件變數的地址。可以在blcok 執行前釋放物件驗證。(有點毀三觀啊)
- Block block ( ”類“物件):不會對 block 做處理。如果block 是棧block ,執行時依然為棧block. 堆block 同理
堆block : 通過copy 棧block 獲得, 當向棧block copy 時。會對內部引用的物件變數如下處理。
- MyClass *class : block 會retain 內部引用的 物件變數,改變引用物件的記憶體計數。
- Block block( ”類“物件): ”類“物件block 執行copy ,如果是棧block。如果為堆block 並不會對他copy 。
GCD block :會對內部引用的物件變數如下處理。
- MyClass *class: retain 內部引用的物件變數,改變引用物件的記憶體計數。
- Block block( ”類“物件): ”類“物件block 執行copy 。
dispatch_async(dispatch_get_main_queue(), ^{
});
當然如果引用了外部棧block 變數,也會copy 棧block 到堆上。同時棧block 對其內部引用物件變數 重複前面的操作。
這裡不如說 GCD 裡的 block是內部做了處理的堆block 。
以上結論均在 MRC 環境 的 viewDidLoad 方法中測試。
ARC環境下
雖然 ARC 環境下會對 棧block 做優化,當建立一個棧block 時預設返回一個 堆block 。但 並不是ARC 環境下沒有棧block 。
如
-(void)demo:(Block)blocka;
-(void)demo:(Block)blocka{
NSLog(@"==demo=====%@",blocka);
}
通過 方法引數傳遞的 block 就是 棧block。
由此引出 block 迴圈引用問題:
迴圈引用 成立的條件: 直接導致A B 兩個物件相互強引用(retain,strong)、或者 間接導致 A B 兩個物件相互強引用。
直接迴圈引用:
ARC 環境。
ARC 中 LLVM 會監控物件引用情況,如果出現迴圈引用會waring.
第一個警告是間接迴圈引用。
- ViewController -----retain--> Custom
- Custom -------強引用--->CustomBlock(堆block,會retain 內部引用物件變數)
- CustomBlock ---retain--------->self(ViewController)
第二個警告 直接迴圈引用。
- ViewController --強引用------>Block(堆block,會retain 內部引用物件變數)
- Block------retain--------->self(ViewController)
對於第三個 我們常用的GCD block 中,則沒有出現警告。雖然 gcd block 同樣對self 進行了retain.
dispatch_async(dispatch_get_main_queue(), ^{ NSLog(@"====%@",self); });
但是self 並沒有直接或間接的去強引用 gcd block。 可以想象 gcd block 會統一被管理在GcdBlockDispatchCenter(這個是我瞎扯的,相當於GCD block 排程中心 來管理,反正不是self 。)。
還有一種情況下不會出現迴圈引用 如: ARC 環境
2014-12-30 11:05:59.119 Test_Class[4235:230795] ==demo=====<__NSStackBlock__: 0x118600>
上面說到,棧block 並不會對內部引用物件變數 retain .而是直接引用物件變數地址。、、
- 這裡 self 和 Custom 並沒有對 方法引數blocka 做任何引用操作,blocka 一直存在於棧上,直到執行完畢被釋放掉。
- 當然blocka 也沒有對self 做任何操作,直接引用了地址。
以上 也就是說只有堆block 才會存在迴圈引用的情況。
》》待續.....
複習下記憶體分類: http://blog.csdn.net/u011848617/article/details/39346075