首先來看看block截獲自動變量(外部局部變量)的幾種情況
block“帶有自動變量(截獲局部變量)”的含義在於具有“截獲自動變量值”的能力
ARC中常見的block用法場景如下:
由以上結果總結如下:
- 默認block的類型是NSGlobalBlock,一旦在block中引用自動變量(包括OC對象),無論是否修改,block類型變爲NSMallocBlock
- 默認情況下,在block內不能對截獲的自動變量(或OC對象)進行修改,編譯器會報錯
- 默認情況下,在block中引用的自動變量或OC對象(包括調用OC對象方法),block中的自動變量地址不同,而引用前後自動變量的地址一致
- 在自動變量(或OC對象)前添加__block,無論是否修改自動變動,引用後與block自動變量的地址一致,而且與引用前不一致
另外,需要注意的一點是,block目前沒有實現對C語言數組的截獲,例如以下代碼會產生編譯錯誤
使用指針可以解決這個問題
- (void)blockCaptureCArray {
// C 語言的數組
const char *text = "hello";
void (^blockTest) (void) = ^{
NSLog(@"%c",text[0]);
};
blockTest();
}
block的實質
可以通過clang -rewrite-objc 源代碼文件名
將含有block的代碼變換爲C++的源代碼進行分析,將如下main.m文件
#import <stdio.h>
int main() {
void (^blk) (void) = ^{
printf("Block\n");
};
blk();
return 0;
}
打開終端,在main.m所在目錄下鍵入clang -rewrite-objc main.m即可在當前目錄下生成一個main.cpp文件,主要分爲:
1、__block_impl結構體定義
// 結構體 __block_impl 的聲明
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
2、__main_block_impl_0結構體定義,其中包含其構造函數
參數fp傳入__main_block_func_0函數的地址,賦值給__block_impl類型的結構體實例impl的函數指針FuncPtr
參數desc傳入的&__main_block_desc_0_DATA結構體取地址賦值給了__main_block_desc_0類型的結構體指針Desc
__block_impl類型的結構體實例impl的isa指針存放了_NSConcreteStackBlock類的地址,旨在將block作爲OC的對象處理,將block類的信息放置於_NSConcreteStackBlock,由此可見,block的實質是OC對象
// 結構體 __main_block_impl_0 的聲明
struct __main_block_impl_0 {
// 成員變量 impl
struct __block_impl impl;
// 指針
struct __main_block_desc_0* Desc;
// 結構體 __main_block_impl_0 的構造函數
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
3、block函數轉化後的源代碼
// __cself 是 __main_block_impl_0 結構體的指針
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
// block的描述
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)
};
4、程序執行的main函數
/* main.m 中 main 函數轉換後的源代碼*/
int main() {
// 左邊是一個無參無返回值的函數指針
// 右邊__main_block_impl_0結構體包含自己的屬性、構造方法、成員方法,然後取其地址
void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 代碼轉換1
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *blk = &tmp;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
// 對應代碼 block();
// 前半部分訪問結構體 __block_impl 自己的函數指針 FuncPtr,並且將 block 自身作爲參數傳遞
// 代碼轉換2
(*blk->impl.FuncPtr)(blk);
return 0;
}
又見block(二):block語法定義
又見block(四):block捕獲自動變量
又見block(五): __block變量和對象
又見block(六):block存儲域與__block變量存儲域
又見block(七):截獲對象