深刻理解Block之Block的類型

iOS-Source-Code-Analyse 首發
Follow: sunbohong· Githubhtml

深刻理解Block之Block的類型

    當我在 2012 年剛剛開始從事 iOS 開發工做時,對 Block 的使用開始逐漸在 iOS 開發者中推廣開來(Block 的第一個穩定 ABI 版本是在 Mac OS X 10.6 被引入的。)。做爲 iOS 開發中很是吸引個人一個特性,對其的深刻分析天然必不可少。c++

 

重要聲明:雖然我已經仔細的檢查了本身的相關代碼和相關的措辭,可是請不要盲目相信本文的正確性。我已經見過很是多的經驗開發者對於 Block 有錯誤的理解(我也不會例外)。請必定保持一顆懷疑的心。git

類型簡介

對 block 稍微有所瞭解的人都知道,block 會在編譯過程當中,會被當作結構體進行處理。 其結構Block-ABI-Apple大概是這樣的:this

struct Block_literal_1 {
    void *isa; // initialized to &_NSConcreteStackBlock or &_NSConcreteGlobalBlock
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 {
    unsigned long int reserved;         // NULL
        unsigned long int size;         // sizeof(struct Block_literal_1)
        // optional helper functions
        void (*copy_helper)(void *dst, void *src);     // IFF (1<<25)
        void (*dispose_helper)(void *src);             // IFF (1<<25)
        // required ABI.2010.3.16
        const char *signature;                         // IFF (1<<30)
    } *descriptor;
    // imported variables
};

isa 指針會指向 block 所屬的類型,用於幫助運行時系統進行處理。

Block 常見的類型有三種,分別是 _NSConcreteStackBlock _NSConcreteMallocBlock _NSConcreteGlobalBlock

另外還包括只在GC環境下使用的 _NSConcreteFinalizingBlock _NSConcreteAutoBlock _NSConcreteWeakBlockVariable

下面摘自 libclosure-65 - Block_private.h-213

// 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);
// declared in Block.h
// BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
// BLOCK_EXPORT void * _NSConcreteStackBlock[32];

_NSConcreteGlobalBlock & _NSConcreteStackBlock

_NSConcreteGlobalBlock & _NSConcreteStackBlock 是 block 初始化時設置的類型(上文中 Block-ABI-Apple 已經說起,而且 CGBlocks_8cpp_source.html#l00141 也提到過)。

在如下狀況中,block 會初始化爲 _NSConcreteGlobalBlock

  • 未捕獲外部變量。
    static void computeBlockInfo(CodeGenModule &CGM, CodeGenFunction *CGF,CGBlockInfo &info) 函數內的 334行339行,經過判斷 block(以及嵌套的block) 是否捕捉了本地存儲(原文爲:local storage),未捕獲時,block 會初始化爲 _NSConcreteGlobalBlock

    if (!block->hasCaptures()) {
      info.StructureType =
        llvm::StructType::get(CGM.getLLVMContext(), elementTypes, true);
      info.CanBeGlobal = true;
      return;
    }
  • 當須要佈局(layout)的變量的數量爲0時。
    static void computeBlockInfo(CodeGenModule &CGM, CodeGenFunction *CGF,CGBlockInfo &info)函數內,經過計算 block 的佈局(layout)。當須要佈局的變量爲0時,block 會初始化爲 _NSConcreteGlobalBlock

    統計須要佈局(layout)的變量:

    • this (爲了訪問 c++ 的成員變量和函數,須要 this 指針)

    • 依次按下列規則處理捕獲的變量:

      • 不須要計算佈局的變量:

        • 生命週期爲靜態的變量(被 const static 修飾的變量,不被函數包含的靜態常量,c++中生命週期爲靜態的變量)

        • 函數參數

      • 須要計算佈局的變量:被 __block 修飾的變量,以上未提到的類型(好比block)

     
    Tips:當須要佈局(layout)的變量的統計完畢後,會按照如下順序進行一次穩定排序。
     

    • __strong 修飾的變量

    • ByRef 類型

    • __weak 修飾的變量

    • 其它類型

_NSConcreteMallocBlock

在非垃圾收集環境下,當 _NSConcreteStackBlock 類型的block 被真正複製時,在 _Block_copy_internal 方法內部,會轉換爲 _NSConcreteMallocBlock libclosure-65/runtime.c

// Its a stack block.  Make a copy.
if (!isGC) {
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return NULL;
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // reset refcount
    result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
    result->isa = _NSConcreteMallocBlock;
    _Block_call_copy_helper(result, aBlock);
    return result;
}

_NSConcreteFinalizingBlock&_NSConcreteAutoBlock

在垃圾收集環境下,當 block 被複制時,若是block 有 ctors & dtors 時,則會轉換爲 _NSConcreteFinalizingBlock 類型,反之,則會轉換爲 _NSConcreteAutoBlock 類型

if (hasCTOR) {
    result->isa = _NSConcreteFinalizingBlock;
}
else {
    result->isa = _NSConcreteAutoBlock;
}

_NSConcreteWeakBlockVariable

GC環境下,當對象被 __weak __block 修飾,且從棧複製到堆時,block 會被標記爲 _NSConcreteWeakBlockVariable 類型。

bool isWeak = ((flags & (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK)) == (BLOCK_FIELD_IS_BYREF|BLOCK_FIELD_IS_WEAK));
// if its weak ask for an object (only matters under GC)
struct Block_byref *copy = (struct Block_byref *)_Block_allocator(src->size, false, isWeak);
copy->flags = src->flags | _Byref_flag_initial_value; // non-GC one for caller, one for stack
copy->forwarding = copy; // patch heap copy to point to itself (skip write-barrier)
src->forwarding = copy;  // patch stack to point to heap copy
copy->size = src->size;
if (isWeak) {
  copy->isa = &_NSConcreteWeakBlockVariable;  // mark isa field so it gets weak scanning
}

ARC環境的特殊處理

下面的代碼均經過添加 objc_retainBlock _Block_copy_Block_copy_internal 符號斷點進行測試

  • 在 ARC 下,block 類型經過=進行傳遞時,會致使調用objc_retainBlock->_Block_copy->_Block_copy_internal方法鏈。並致使 __NSStackBlock__ 類型的 block 轉換爲 __NSMallocBlock__ 類型。objc4-680/runtime/NSObject.mm-193 說起到了這一點。

    //
    // The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
    //
    
    id objc_retainBlock(id x) {
        return (id)_Block_copy(x);
    }
    
    ...
    
    void *_Block_copy(const void *arg) {
       return _Block_copy_internal(arg, true);
    }

    測試代碼:

    void test() {
        __block int i = 0;
        dispatch_block_t block  = ^(){NSLog(@"%@", @(i)); };
        dispatch_block_t block1 = block;
        NSLog(@"初始化爲變量後再打印:%@", block1);
    
        NSLog(@"直接打印:%@", ^(){NSLog(@"%@", @(i)); });
    }

    日誌:

    "objc_retainBlock 函數被調用"
    
    "_Block_copy 函數被調用"
    
    "_Block_copy_internal 函數被調用"
    
    "objc_retainBlock 函數被調用"
    
    "_Block_copy 函數被調用"
    
    "_Block_copy_internal 函數被調用"
    
    初始化爲變量後再打印:<__NSMallocBlock__: 0x7fb05b605800>
    直接打印:<__NSStackBlock__: 0x7fff55ccc568>
  • 在 ARC 下,不一樣的屬性修飾符以及不一樣賦值、取值方式均會對方法調用產生影響。下表爲測試結果。

\ strong retain copy
直接賦值 _Block_copy->_Block_copy_internal _Block_copy->_Block_copy_internal
間接賦值 _Block_copy->_Block_copy_internal _Block_copy->_Block_copy_internal _Block_copy->_Block_copy_internal
經過屬性取值 _Block_copy->_Block_copy_internal-> _Block_copy->_Block_copy_internal _Block_copy->_Block_copy_internal-> _Block_copy->_Block_copy_internal _Block_copy->_Block_copy_internal-> _Block_copy->_Block_copy_internal _Block_copy->_Block_copy_internal-> _Block_copy->_Block_copy_internal
經過變量取值

直接賦值:

NSString *str = @"sun";
dispatch_block_t block = ^(){
    NSLog(@"%@", str);
};
self.block = block;

間接賦值:

self.block = ^(){
    NSLog(@"%@", str);
};

經過屬性取值

self.block

經過變量取值

self->_block

測試代碼:

- (void)test {

      NSString *str = @"sun";
      {
          NSLog(@"直接賦值開始");
          {
              self.copyBlock = ^(){
                  NSLog(@"%@", str);
              };

              NSLog(@"copy 屬性修飾的 block:%@", self->_copyBlock);
          }
          {
              self.strongBlock = ^(){
                  NSLog(@"%@", str);
              };

              NSLog(@"strong 屬性修飾的 block:%@", self->_strongBlock);
          }
          {
              self.retainBlock = ^(){
                  NSLog(@"%@", str);
              };

              NSLog(@"retain 屬性修飾的 block:%@", self->_retainBlock);
          }
          NSLog(@"直接賦值結束");
      }
      {
          dispatch_block_t copyBlock = ^(){
              NSLog(@"%@", str);
          };
          dispatch_block_t strongBlock = ^(){
              NSLog(@"%@", str);
          };
          dispatch_block_t retainBlock = ^(){
              NSLog(@"%@", str);
          };
          NSLog(@"間接賦值開始");
          {
              self.copyBlock = copyBlock;

              NSLog(@"copy 屬性修飾的 block:%@", self->_copyBlock);
          }
          {
              self.strongBlock = strongBlock;

              NSLog(@"strong 屬性修飾的 block:%@", self->_strongBlock);
          }
          {
              self.retainBlock = retainBlock;

              NSLog(@"retain 屬性修飾的 block:%@", self->_retainBlock);
          }
          NSLog(@"間接賦值結束");
      }
      {
          NSLog(@"經過屬性獲取開始");
          {
              NSLog(@"copy 屬性修飾的 block:%@", self.copyBlock);

              NSLog(@"strong 屬性修飾的 block:%@", self.strongBlock);

              NSLog(@"retain 屬性修飾的 block:%@", self.retainBlock);
          }

          NSLog(@"獲取結束");
      }

      {
          NSLog(@"經過變量獲取開始");
          {
              NSLog(@"copy 屬性修飾的 block:%@", self->_copyBlock);

              NSLog(@"strong 屬性修飾的 block:%@", self->_strongBlock);

              NSLog(@"retain 屬性修飾的 block:%@", self->_retainBlock);
          }

          NSLog(@"獲取結束");
      }
  }

日誌:

間接賦值開始


"_Block_copy 函數被調用"

"_Block_copy_internal 函數被調用"

copy 屬性修飾的 block:<__NSMallocBlock__: 0x7fab00fa4c30>


"_Block_copy 函數被調用"

"_Block_copy_internal 函數被調用"

strong 屬性修飾的 block:<__NSMallocBlock__: 0x7fab00d1a970>


"_Block_copy 函數被調用"

"_Block_copy_internal 函數被調用"

retain 屬性修飾的 block:<__NSMallocBlock__: 0x7fab00f08ad0>


間接賦值結束


經過屬性獲取開始


"_Block_copy 函數被調用"

"_Block_copy_internal 函數被調用"

"_Block_copy 函數被調用"

"_Block_copy_internal 函數被調用"

copy 屬性修飾的 block:<__NSMallocBlock__: 0x7fab00fa4c30>


"_Block_copy 函數被調用"

"_Block_copy_internal 函數被調用"

"_Block_copy 函數被調用"

"_Block_copy_internal 函數被調用"

strong 屬性修飾的 block:<__NSMallocBlock__: 0x7fab00d1a970>


"_Block_copy 函數被調用"

"_Block_copy_internal 函數被調用"

"_Block_copy 函數被調用"

"_Block_copy_internal 函數被調用"

retain 屬性修飾的 block:<__NSMallocBlock__: 0x7fab00f08ad0>


獲取結束

經過變量獲取開始

copy 屬性修飾的 block:<__NSMallocBlock__: 0x7fab00fa4c30>
strong 屬性修飾的 block:<__NSMallocBlock__: 0x7fab00d1a970>
retain 屬性修飾的 block:<__NSMallocBlock__: 0x7fab00f08ad0>

獲取結束
(lldb)
相關文章
相關標籤/搜索