block 對外部引用變數的處理

hhhker發表於2014-12-05

                              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

相關文章