block 顧名思義就是代碼塊,將同一邏輯的代碼放在一個塊,使代碼更簡潔緊湊,易於閱讀,而且它比函數使用更方便,代碼更美觀,因而廣受開發者歡迎。但同時 block 也是 iOS 開發中坑最多的地方之一,因此有必要了解下 block 的實現原理,知其然,更知其所以然,才能從根本上避免挖坑和踩坑。
需要知道的是,block 只是 Objective-C 對閉包的實現,並不是 iOS 獨有的概念,在 C++、Java 等語言也有實現閉包,名稱不同而已。
特別聲明
以下研究所用的過程代碼由 clang 編譯前端生成,僅作理解之用。實際上 clang 根本不會將 block 轉換成人類可讀的代碼,它對 block 到底做了什麼,誰也不知道。
所以,切勿將過程代碼當做block的實際實現,切記切記!!!
將下面的 test.m 代碼用 clang 工具翻譯 test.cpp 代碼
clang -rewrite-objc test.m
test.m 代碼
/************* Objective-C 源碼 *************/
int main()
{
void (^blk)(void) = ^{ printf("Block\n"); };
blk();
return 0;
}
test.cpp
/************* 使用 clang 翻譯後如下 *************/
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;
__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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("Block\n");
}
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) };
int main()
{
void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA);
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
接着,我們逐一來看下這些函數和結構體
block 結構體信息詳解
struct __block_impl
// __block_impl 是 block 實現的結構體
struct __block_impl
{
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
isa
指向實例對象,表明 block 本身也是一個 Objective-C 對象。block 的三種類型:_NSConcreteStackBlock
、_NSConcreteGlobalBlock
、_NSConcreteMallocBlock
,即當代碼執行時,isa 有三種值impl.isa = &_NSConcreteStackBlock;
impl.isa = &_NSConcreteMallocBlock;
impl.isa = &_NSConcreteGlobalBlock;從這些實例對象可以看出 block 所在內存區域分別爲
stack
、ROData
、heap
,後面文章會詳說。Flags
按位承載 block 的附加信息;Reserved
保留變量;FuncPtr
函數指針,指向 Block 要執行的函數,即{ printf(“Block\n”) };
struct __main_block_impl_0
// __main_block_impl_0 是 block 實現的結構體,也是 block 實現的入口
struct __main_block_impl_0
{
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__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;
}
};
impl
block 實現的結構體變量,該結構體前面已說明;Desc
描述 block 的結構體變量;__main_block_impl_0
結構體的構造函數,初始化結構體變量impl
、Desc
;
static void __main_block_func_0
// __main_block_func_0 是 block 要最終要執行的函數代碼
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
printf("Block\n");
}
static struct __main_block_desc_0
// __main_block_desc_0 是 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) };
reserved
結構體信息保留字段Block_size
結構體大小
此處已定義了一個該結構體類型的變量 __main_block_desc_0_DATA
block 實現的執行流程
最基礎的 block 實現就這麼簡單。
接着再看 block 獲取外部變量
block 獲取外部變量
運行下面的代碼
int main()
{
int intValue = 1;
void (^blk)(void) = ^{ printf("intValue = %d\n", intValue); };
blk();
return 0;
}
打印結果
intValue = 1
和第一段源碼不同的是,這裏多了個局部變量intValue
,而且還在 block 裏面獲取到了。
通過前一段對 block 源碼的學習,我們已經瞭解到 block 的函數定義在 main() 函數之外,那它又是如何獲取 main() 裏面的局部變量呢?爲了解開疑惑,我們再次用 clang 重寫這段代碼
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 intValue;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _intValue, int flags=0) : intValue(_intValue)
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
{
int intValue = __cself->intValue; // bound by copy
printf("intValue = %d\n", intValue);
}
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)};
int main()
{
int intValue = 1;
void (*blk)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, intValue);
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
原來 block 通過參數值傳遞獲取到 intValue
變量,通過函數
__main_block_impl_0 (void *fp, struct __main_block_desc_0 *desc, int _intValue, int flags=0) : intValue(_intValue)
保存到 __main_block_impl_0
結構體的同名變量intValue
,通過代碼int intValue = __cself->intValue;
取出 intValue
,打印出來。
構造函數 __main_block_impl_0 冒號後的表達式 intValue(_intValue) 的意思是,用 _intValue 初始化結構體成員變量 intValue。
有四種情況下應該使用初始化表達式來初始化成員:
1:初始化const成員
2:初始化引用成員
3:當調用基類的構造函數,而它擁有一組參數時
4:當調用成員類的構造函數,而它擁有一組參數時
至此,我們已經瞭解了block 的實現,以及獲取外部變量的原理。但是,我們還不能在 block 內修改 intValue 變量。如果你有心試下,在 block 內部修改 intValue 的值,會報編譯錯誤
Variable is not assignable(missing __block type specifier)
那麼如何在 block 內修改外部變量呢,請看下篇: block 和變量的內存管理(二)