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