五道經典 Block 題目,涉及棧/堆存儲、變量捕獲、ARC 與非 ARC 行為差異。請先思考再看文末答案。
Block 是 C 語言擴展,本質是帶有自動變量(局部變量)的匿名函數。它允許在不聲明類的情況下,用函數式風格保存上下文狀態,極大簡化了回調、代理等場景的代碼。
使用 clang -rewrite-objc 可將 Block 轉為可讀的 C++ 代碼。核心結構:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;
// 捕獲的變量作為結構體字段
int val;
};
Block 實際上是一個 Objective-C 對象(有 isa 指針),捕獲的自動變量以值傳遞方式複製到 Block 結構體中。
int val = 10;
void (^blk)(void) = ^{ printf("val=%d\n", val); };
val = 2;
blk(); // 輸出 10,而非 2
Block 捕獲的是創建時刻的瞬時值。捕獲的自動變量在 Block 內部實質變為 const,不可修改,否則編譯報錯。
__block int val = 10;
void (^blk)(void) = ^{ val = 1; };
加上 __block 後,變量會被包裝成一個結構體 __block_byref_val_0,包含 __forwarding 指針指向自身。這使得 Block 可修改變量,且無論 Block 在棧上還是複製到堆上,都能正確訪問同一份內存。
| 類型 | 說明 |
|---|---|
_NSConcreteStackBlock | 棧上,函數內部且捕獲自動變量時(非 ARC 默認) |
_NSConcreteGlobalBlock | 全局區,函數外部定義,或函數內部但未捕獲任何自動變量 |
_NSConcreteMallocBlock | 堆上,調用 copy 後,或 ARC 環境下捕獲自動變量自動複製到堆 |
copy 無效果;棧 Block copy 會複製到堆;堆 Block copy 僅增加引用計數。copy。copy 無副作用,ARC 會自動管理釋放。當 Block 被複製到堆時,它捕獲的 __block 變量也會一同複製到堆。結構體中的 __forwarding 指針確保棧上和堆上的 Block 都能正確訪問同一份變量內存。
Block 會強持有捕獲的 Objective-C 對象。若對象又持有 Block,會產生循環引用。解決方案:
__weak 修飾符。__weak typeof(self) weakSelf = self;__block 並在 Block 內置 nil:__block typeof(self) tmp = self; ... tmp = nil;(需記得置 nil,不推薦)。ABABB