深入理解Block之Block的類型

iOS-Source-Code-Analyse 首發(fā)
Follow: sunbohong· Github

深入理解Block之Block的類型

當我在 2012 年剛剛開始從事 iOS 開發(fā)工作時妄均,對 Block 的使用開始逐漸在 iOS 開發(fā)者中推廣開來(Block 的第一個穩(wěn)定 ABI 版本是在 Mac OS X 10.6 被引入的峭跳。)。作為 iOS 開發(fā)中非常吸引我的一個特性斟湃,對其的深入分析自然必不可少料滥。

重要聲明:雖然我已經(jīng)仔細的檢查了自己的相關(guān)代碼和相關(guān)的措辭,但是請不要盲目相信本文的正確性。我已經(jīng)見過非常多的經(jīng)驗開發(fā)者對于 Block 有錯誤的理解(我也不會例外)躺涝。請一定保持一顆懷疑的心。

<a name="1"></a>類型簡介

對 block 稍微有所了解的人都知道,block 會在編譯過程中坚嗜,會被當做結(jié)構(gòu)體進行處理夯膀。 其結(jié)構(gòu)Block-ABI-Apple大概是這樣的:

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 所屬的類型,用于幫助運行時系統(tǒng)進行處理苍蔬。

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

另外還包括只在GC環(huán)境下使用的 _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];

<a name="1.1"></a>_NSConcreteGlobalBlock & _NSConcreteStackBlock

_NSConcreteGlobalBlock & _NSConcreteStackBlock 是 block 初始化時設(shè)置的類型(上文中 Block-ABI-Apple 已經(jīng)提及俺猿,并且 CGBlocks_8cpp_source.html#l00141 也提到過)。

在以下情況中格仲,block 會初始化為 _NSConcreteGlobalBlock

 if (!block->hasCaptures()) {
   info.StructureType =
     llvm::StructType::get(CGM.getLLVMContext(), elementTypes, true);
   info.CanBeGlobal = true;
   return;
 }
統(tǒng)計需要布局(layout)的變量:
* `this` (為了訪問 `c++` 的成員變量和函數(shù),需要 `this` 指針)
* 依次按下列規(guī)則處理捕獲的變量:
    * 不需要計算布局的變量:
        * 生命周期為靜態(tài)的變量(被 `const` `static` 修飾的變量宽闲,不被函數(shù)包含的靜態(tài)常量众眨,c++中生命周期為靜態(tài)的變量)
        * 函數(shù)參數(shù)
    * 需要計算布局的變量:被 `__block` 修飾的變量,以上未提到的類型(比如block)

 
**Tips**:當需要布局(layout)的變量的統(tǒng)計完畢后便锨,會按照以下順序進行一次穩(wěn)定排序围辙。
 
*  __strong 修飾的變量
*  ByRef 類型
*  __weak 修飾的變量
*  其它類型

<a name="1.2"></a>_NSConcreteMallocBlock

在非垃圾收集環(huán)境下,當 _NSConcreteStackBlock 類型的block 被真正復(fù)制時放案,在 _Block_copy_internal 方法內(nèi)部姚建,會轉(zhuǎn)換為 _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;
}

<a name="1.3"></a>_NSConcreteFinalizingBlock&_NSConcreteAutoBlock

在垃圾收集環(huán)境下,當 block 被復(fù)制時吱殉,如果block 有 ctors & dtors 時掸冤,則會轉(zhuǎn)換為 _NSConcreteFinalizingBlock 類型,反之友雳,則會轉(zhuǎn)換為 _NSConcreteAutoBlock 類型

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

_NSConcreteWeakBlockVariable

GC環(huán)境下稿湿,當對象被 __weak __block 修飾,且從棧復(fù)制到堆時押赊,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
}

<a name="2"></a>ARC環(huán)境的特殊處理

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

  • 在 ARC 下,block 類型通過=進行傳遞時,會導(dǎo)致調(diào)用objc_retainBlock->_Block_copy->_Block_copy_internal方法鏈涕俗。并導(dǎo)致 __NSStackBlock__ 類型的 block 轉(zhuǎn)換為 __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 函數(shù)被調(diào)用"

"_Block_copy 函數(shù)被調(diào)用"

"_Block_copy_internal 函數(shù)被調(diào)用"

"objc_retainBlock 函數(shù)被調(diào)用"

"_Block_copy 函數(shù)被調(diào)用"

"_Block_copy_internal 函數(shù)被調(diào)用"

初始化為變量后再打印:<__NSMallocBlock__: 0x7fb05b605800>
直接打釉啤:<__NSStackBlock__: 0x7fff55ccc568>

  • 在 ARC 下绍填,不同的屬性修飾符以及不同賦值、取值方式均會對方法調(diào)用產(chǎn)生影響栖疑。下表為測試結(jié)果讨永。
\ 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(@"直接賦值結(jié)束");
    }
    {
        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(@"間接賦值結(jié)束");
    }
    {
        NSLog(@"通過屬性獲取開始");
        {
            NSLog(@"copy 屬性修飾的 block:%@", self.copyBlock);

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

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

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

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

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

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

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

日志:


間接賦值開始


"_Block_copy 函數(shù)被調(diào)用"

"_Block_copy_internal 函數(shù)被調(diào)用"

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


"_Block_copy 函數(shù)被調(diào)用"

"_Block_copy_internal 函數(shù)被調(diào)用"

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


"_Block_copy 函數(shù)被調(diào)用"

"_Block_copy_internal 函數(shù)被調(diào)用"

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


間接賦值結(jié)束


通過屬性獲取開始


"_Block_copy 函數(shù)被調(diào)用"

"_Block_copy_internal 函數(shù)被調(diào)用"

"_Block_copy 函數(shù)被調(diào)用"

"_Block_copy_internal 函數(shù)被調(diào)用"

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


"_Block_copy 函數(shù)被調(diào)用"

"_Block_copy_internal 函數(shù)被調(diào)用"

"_Block_copy 函數(shù)被調(diào)用"

"_Block_copy_internal 函數(shù)被調(diào)用"

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


"_Block_copy 函數(shù)被調(diào)用"

"_Block_copy_internal 函數(shù)被調(diào)用"

"_Block_copy 函數(shù)被調(diào)用"

"_Block_copy_internal 函數(shù)被調(diào)用"

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


獲取結(jié)束

通過變量獲取開始

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

獲取結(jié)束
(lldb) 
  
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市蔽挠,隨后出現(xiàn)的幾起案子住闯,更是在濱河造成了極大的恐慌,老刑警劉巖澳淑,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異插佛,居然都是意外死亡杠巡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門雇寇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來氢拥,“玉大人,你說我怎么就攤上這事锨侯∧酆#” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵囚痴,是天一觀的道長叁怪。 經(jīng)常有香客問我,道長深滚,這世上最難降的妖魔是什么奕谭? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮痴荐,結(jié)果婚禮上血柳,老公的妹妹穿的比我還像新娘。我一直安慰自己生兆,他們只是感情好难捌,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般根吁。 火紅的嫁衣襯著肌膚如雪员淫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天婴栽,我揣著相機與錄音满粗,去河邊找鬼。 笑死愚争,一個胖子當著我的面吹牛映皆,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播轰枝,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼捅彻,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鞍陨?” 一聲冷哼從身側(cè)響起步淹,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎诚撵,沒想到半個月后缭裆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡寿烟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年澈驼,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筛武。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡缝其,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出徘六,到底是詐尸還是另有隱情内边,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布待锈,位于F島的核電站漠其,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏炉擅。R本人自食惡果不足惜辉懒,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望谍失。 院中可真熱鬧眶俩,春花似錦、人聲如沸快鱼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至线罕,卻和暖如春止潮,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背钞楼。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工喇闸, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人询件。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓燃乍,卻偏偏與公主長得像,于是被迫代替她去往敵國和親宛琅。 傳聞我的和親對象是個殘疾皇子刻蟹,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

推薦閱讀更多精彩內(nèi)容