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
:
-
未捕獲外部變量押袍。
在 static void computeBlockInfo(CodeGenModule &CGM, CodeGenFunction *CGF,CGBlockInfo &info) 函數(shù)內(nèi)的 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)的變量的數(shù)量為0時侮东。
在
static void computeBlockInfo(CodeGenModule &CGM, CodeGenFunction *CGF,CGBlockInfo &info)函數(shù)內(nèi)圈盔,通過計算 block 的布局(layout)。當需要布局的變量為0時悄雅,block 會初始化為_NSConcreteGlobalBlock
驱敲。
統(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)