Block型別及儲存區域

Angelia_瀅發表於2018-05-07

#序言

Block前言中,講到Block 的isa指標六種型別,以及每種型別的儲存區域。簡單回顧一下最終結論

型別 檢視源 儲存區域
_NSConcreteGlobalBlock .cpp檔案/Block.h 全域性變數/靜態變數區
_NSConcreteStackBlock .cpp檔案/Block.h 棧區
_NSConcreteMallocBlock Block_private.h檔案 堆區
_NSConcreteAutoBlock Block_private.h檔案 堆區
_NSConcreteFinalizingBlock Block_private.h檔案 堆區
_NSConcreteWeakBlockVariable Block_private.h檔案 堆區

以上內容的檢視在編譯後的cpp檔案以及runtime原始碼中都可以檢視到相關資訊 code已上傳到Github,點選下載

runtime原始碼Apple官網

runtime原始碼Github

#特別注意點

Block前言中講到預設建立的Block指標只有Global、Statck,其它四種是在執行時編譯環境決定的,此根據也是根據runtime原始碼和註釋得出結論。

// the raw data space for runtime classes for blocks
// class+meta used for stack, malloc, and collectable based blocks
BLOCK_EXPORT void * _NSConcreteMallocBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteAutoBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32]
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);
複製程式碼

從以上runtime原始碼和註釋中我們可以很好的解釋該6種Block在Mac和iOS系統中都會出現,除Global、Statck,其它四種是在執行時編譯環境決定。

#每種Block出現的情況

(最好複習一下基本資料結構)

##_NSConcreteGlobalBlock

在以下情況下,Block為GlobalBlock (根據賦值情況會決定儲存靜態區的靜態變數區域還是全域性變數區域) 1.Block宣告為全域性變數 2.函式區域內Block語法表示式沒有使用外部變數 3.Block內部只引用了內部傳遞值或只引用了靜態變數或全域性變數

第一種情況好理解,一般我們宣告的全域性變數都會放在靜態區,當Block被宣告為全域性變數時也會存放在靜態區域 如下將Block宣告為全域性變數,檢視編譯後的cpp檔案,找到對應的編譯程式碼 原始碼:

#import "BlockObject.h"
#import <objc/runtime.h>

void (^globalBlock)(void)=^{};

@interface BlockObject()
複製程式碼

編譯後的部分程式碼:

struct __globalBlock_block_impl_0 {
  struct __block_impl impl;
  struct __globalBlock_block_desc_0* Desc;
  __globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
複製程式碼

`impl.isa = &_NSConcreteGlobalBlock;可以看到我們宣告的全域性變數block為NSConcreteGlobalBlock

第二種情況我們可以看如下程式碼

typedef int (^blockStatic)(void);
blockStatic secondBlk = ^(){
        return 1;
 };
NSLog(@"%@",secondBlk);
複製程式碼

最後檢視到輸出結果如下

YAObjectTest[8834:1196308]
 <__NSGlobalBlock__: 0x10de381c8>
複製程式碼

所以,未引用外部變數時,最終得到的block也是NSConcreteGlobalBlock

第三種情況我看可以看如下程式碼

static int count = 100;
typedef int (^blockStatic)(void);
blockStatic blk = ^(){
    return count;
};
NSLog(@"%@",blk);
複製程式碼

最後的輸出結果如下:

YAObjectTest[8719:1185296]
 <__NSGlobalBlock__: 0x108320188>
複製程式碼

最後的輸出也是GlobalBlock,所以,只引用靜態變數或全域性變數時,Block仍為NSConcreteGlobalBlock


解惑:第二種情況和第三種情況我們最終檢視的是輸出情況,而不是clang後的原始檔。檢視clang後的原始檔該兩種情況得到的是StackBlock。編譯器根據編譯特性會把後兩種情況預設編譯後定義為StackBlock,但是我們知道OC是動態語言,所以最終還是以執行時或最終輸出結果為準。


#_NSConcreteStackBlock

1.使用到外部區域性變數、成員屬性變數(非靜態變數值)& 2.未使用strong或copy修飾符修飾 以上條件缺一不可,只有這兩個條件同時成立時,Block才為_NSConcreteStackBlock 如果在函式內,Block只是引用了外部區域性變數或成員屬性變數,最終會被copy到堆上變為MallocBlock。而對於宣告為屬性的Block,如果修飾符為strong或copy,則也會copy到堆上,而不是StackBlock。 檢視以下程式碼的最終輸出(不能同時滿足上述兩種條件的情況下)

@interface BlockObject()

@property (nonatomic, strong)void(^proBlock)(void);
@property (nonatomic, assign)NSInteger outsideCount;

@end

     int a = 1;
    //使用外部區域性變數情況
    void (^blockVariable)(void) = ^(){
        NSLog(@"%ld",(long)_outsideCount);
    };
    NSLog(@"%@",blockVariable);
    
    _proBlock = ^(){
        NSLog(@"%d",a);
    };
    NSLog(@"%@",_proBlock);
複製程式碼

最終控制檯輸出如下

 <__NSMallocBlock__: 0x600000447c50>
 <__NSMallocBlock__: 0x6040002557b0>

複製程式碼

將proBlock的修飾符換成weak,

@property (nonatomic, weak)void(^proBlock)(void);
複製程式碼

同樣的程式碼,會得到如下結果,proBlock會是StackBlock

YAObjectTest[10461:1384618] <__NSMallocBlock__: 0x60400025d1f0>
YAObjectTest[10461:1384618] <__NSStackBlock__: 0x7ffee9f07930>
複製程式碼

所以,在引用了外部變數並且沒有強指標引用的情況下的Block為_NSConcreteStackBlock

#_NSConcreteMallocBlock

1.引用了外部變數,有strong或copy修飾符修飾 2.block內部需要修改引用變數的值,外部變數被__block修飾 第一種情況在求證StackBlock的情況下已經得到驗證,有strong或copy修飾後並且引用了外部變數的情況下,為** <NSMallocBlock: 0x6040002557b0>** 有關第二種情況,在該文只做驗證,後續深入解析,詳見Block截獲變數中的__block修飾符深入解析

    __block int a = 0;
    void (^lockBlock)(void) = ^{
        a++;
    };
    lockBlock();
    NSLog(@"%@", lockBlock);
複製程式碼

最後的輸出結果為

YAObjectTest[10853:1425358] <__NSMallocBlock__: 0x60000024e610>
複製程式碼

在此就求證了以上兩種結果最後Block為_NSConcreteMallocBlock。


接下來的三種型別Block,因對有GC回收機制的語言不是太熟悉,在Xcode中編寫的C++程式碼 最終檢視執行時的isa指標皆為—NSStackBlock,在Xcode中並沒有按照預想的檢視到以下三種型別,Terminal終端g++ 編譯後的可執行檔案也未見以下型別,查閱資料發現以下三種出現的情況大致如下,若有對C++ 熟悉的人員 ,請不吝賜教?


#_NSConcreteFinalizingBlock

.Block需要copy到堆上,但是Block內部有ctors 和 dtors時,block會是NSFinalizingBlock #_NSConcreteAutoBlock

.Block需要copy到堆上若未引用到ctors和 dtors 則是NSAutoBlock 以上是AutoBlock和FinalizingBlock的出現情況。而對於ctors和dtors, ctors中儲存著程式全部建構函式的指標陣列,dtors中儲存著程式全部解構函式的指標陣列,從兩者的儲存內容來看,若block引用了該兩種型別,勢必block會在程式執行結束時回收記憶體,所以會被轉換為FinalizingBlock,而未引用的block則會根據情況自動回收,轉換為AutoBlock。

以下是寫在cpp檔案中的程式碼

void testAutoBlock(){
  int * b=new int[4];
   __block int testCount = 100;
   int (^myBlock)() = ^() {
        b[0] = testCount;
        b[1] = testCount + 1;
        b[2] = testCount + 2;
        b[3] = testCount + 3;
        std::cout<<b<<std::endl;
       return 0;
    };
    std::cout<<myBlock<<std::endl;
   };
複製程式碼

#_NSConcreteWeakBlockVariable

.GC回收機制下,用_weak 或__block修飾的block 會轉變成NSWeakBlockVariable 具體的對於__weak 和__blcok修飾符,在後面截獲變數和迴圈引用中具體詳解。 以上是個人對於Block型別和儲存區域的總結。中間為探究後三種型別浪費了很多時間,導致總結文章延遲。

mine_wxpay@2x.jpeg
倘若覺得文章還可以,各位大神給支雪糕吧!

相關文章