iOS-Source-Code-Analyse 首發
Follow: sunbohong· Github
深入理解Block之Block的型別
當我在 2012 年剛剛開始從事 iOS 開發工作時,對 Block 的使用開始逐漸在 iOS 開發者中推廣開來(Block 的第一個穩定 ABI 版本是在 Mac OS X 10.6 被引入的。)。作為 iOS 開發中非常吸引我的一個特性,對其的深入分析自然必不可少。
重要宣告:雖然我已經仔細的檢查了自己的相關程式碼和相關的措辭,但是請不要盲目相信本文的正確性。我已經見過非常多的經驗開發者對於 Block 有錯誤的理解(我也不會例外)。請一定保持一顆懷疑的心。
型別簡介
對 block 稍微有所瞭解的人都知道,block 會在編譯過程中,會被當做結構體進行處理。 其結構Block-ABI-Apple大概是這樣的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
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
1 2 3 4 5 6 7 8 9 10 11 12 13 |
// 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
。
1 2 3 4 5 6 |
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
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
統計需要佈局(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
1 2 3 4 5 6 7 8 9 10 11 12 |
// 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
型別
1 2 3 4 5 6 |
if (hasCTOR) { result->isa = _NSConcreteFinalizingBlock; } else { result->isa = _NSConcreteAutoBlock; } |
_NSConcreteWeakBlockVariable
GC環境下,當物件被 __weak __block
修飾,且從棧複製到堆時,block 會被標記為 _NSConcreteWeakBlockVariable
型別。
1 2 3 4 5 6 7 8 9 10 |
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 提及到了這一點。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// // 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); } ``` |
1 |
測試程式碼:
1 2 3 4 5 6 7 8 |
void test() { __block int i = 0; dispatch_block_t block = ^(){NSLog(@"%@", @(i)); }; dispatch_block_t block1 = block; NSLog(@"初始化為變數後再列印:%@", block1); NSLog(@"直接列印:%@", ^(){NSLog(@"%@", @(i)); }); } |
日誌:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
"objc_retainBlock 函式被呼叫" "_Block_copy 函式被呼叫" "_Block_copy_internal 函式被呼叫" "objc_retainBlock 函式被呼叫" "_Block_copy 函式被呼叫" "_Block_copy_internal 函式被呼叫" 初始化為變數後再列印: 直接列印: |
- 在 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 |
通過變數取值 | 無 | 無 | 無 |
直接賦值:
1 2 3 4 5 |
NSString *str = @"sun"; dispatch_block_t block = ^(){ NSLog(@"%@", str); }; self.block = block; |
間接賦值:
1 2 3 |
self.block = ^(){ NSLog(@"%@", str); }; |
通過屬性取值
1 |
self.block |
通過變數取值
1 |
self->_block |
測試程式碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 |
- (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(@"獲取結束"); } } |
日誌:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 |
間接賦值開始 "_Block_copy 函式被呼叫" "_Block_copy_internal 函式被呼叫" copy 屬性修飾的 block: "_Block_copy 函式被呼叫" "_Block_copy_internal 函式被呼叫" strong 屬性修飾的 block: "_Block_copy 函式被呼叫" "_Block_copy_internal 函式被呼叫" retain 屬性修飾的 block: 間接賦值結束 通過屬性獲取開始 "_Block_copy 函式被呼叫" "_Block_copy_internal 函式被呼叫" "_Block_copy 函式被呼叫" "_Block_copy_internal 函式被呼叫" copy 屬性修飾的 block: "_Block_copy 函式被呼叫" "_Block_copy_internal 函式被呼叫" "_Block_copy 函式被呼叫" "_Block_copy_internal 函式被呼叫" strong 屬性修飾的 block: "_Block_copy 函式被呼叫" "_Block_copy_internal 函式被呼叫" "_Block_copy 函式被呼叫" "_Block_copy_internal 函式被呼叫" retain 屬性修飾的 block: 獲取結束 通過變數獲取開始 copy 屬性修飾的 block: strong 屬性修飾的 block: retain 屬性修飾的 block: 獲取結束 (lldb) |