3.編譯器中的block數據結構
3.1 block的數據結構定義閉包
咱們經過大師文章中的一張圖來講明:函數
上圖這個結構是在棧中的結構,咱們來看看對應的結構體定義:優化
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
|
從上面代碼看出,Block_layout就是對block結構體的定義:spa
isa指針:指向代表該block類型的類。指針
flags:按bit位表示一些block的附加信息,好比判斷block類型、判斷block引用計數、判斷block是否須要執行輔助函數等。code
reserved:保留變量,個人理解是表示block內部的變量數。對象
invoke:函數指針,指向具體的block實現的函數調用地址。blog
descriptor:block的附加描述信息,好比保留變量數、block的大小、進行copy或dispose的輔助函數指針。ip
variables:由於block有閉包性,因此能夠訪問block外部的局部變量。這些variables就是複製到結構體中的外部局部變量或變量的地址。
3.2 block的類型
block有幾種不一樣的類型,每種類型都有對應的類,上述中isa指針就是指向這個類。這裏列出常見的三種類型:
_NSConcreteGlobalBlock:全局的靜態block,不會訪問任何外部變量,不會涉及到任何拷貝,好比一個空的block。例如:
1
2
3
4
5
|
#include int main()
{
^{ printf(
"Hello, World!\n"
); } ();
return
0;
}
|
_NSConcreteStackBlock:保存在棧中的block,當函數返回時被銷燬。例如:
1
2
3
4
5
6
|
#include int main()
{
char a =
'A'
;
^{ printf(
"%c\n"
,a); } ();
return
0;
}
|
_NSConcreteMallocBlock:保存在堆中的block,當引用計數爲0時被銷燬。該類型的block都是由_NSConcreteStackBlock類型的block從棧中複製到堆中造成的。例以下面代碼中,在exampleB_addBlockToArray方法中的block仍是_NSConcreteStackBlock類型的,在exampleB方法中就被複制到了堆中,成爲_NSConcreteMallocBlock類型的block:
1
2
3
4
5
6
7
8
9
10
11
12
|
void exampleB_addBlockToArray(NSMutableArray *array) {
char b =
'B'
;
[array addObject:^{
printf(
"%c\n"
, b);
}];
}
void exampleB() {
NSMutableArray *array = [NSMutableArray array];
exampleB_addBlockToArray(array);
void (^block)() = [array objectAtIndex:0];
block();
}
|
總結一下:
_NSConcreteGlobalBlock類型的block要麼是空block,要麼是不訪問任何外部變量的block。它既不在棧中,也不在堆中,我理解爲它可能在內存的全局區。
_NSConcreteStackBlock類型的block有閉包行爲,也就是有訪問外部變量,而且該block只且只有有一次執行,由於棧中的空間是可重複使用的,因此當棧中的block執行一次以後就被清除出棧了,因此沒法屢次使用。
_NSConcreteMallocBlock類型的block有閉包行爲,而且該block須要被屢次執行。當須要屢次執行時,就會把該block從棧中複製到堆中,供以屢次執行。
3.3 編譯器如何編譯
咱們經過一個簡單的示例來講明:
1
2
3
4
5
6
7
8
9
10
11
|
#import typedef void(^BlockA)(void);
__attribute__((noinline))
void runBlockA(BlockA block) {
block();
}
void doBlockA() {
BlockA block = ^{
// Empty block
};
runBlockA(block);
}
|
上面的代碼定義了一個名爲BlockA的block類型,該block在函數doBlockA中實現,並將其做爲函數runBlockA的參數,最後在函數doBlockA中調用函數runBloackA。
注意:若是block的建立和調用都在一個函數裏面,那麼優化器(optimiser)可能會對代碼作優化處理,從而致使咱們看不到編譯器中的一些操做,因此用__attribute__((noinline))給函數runBlockA添加noinline,這樣優化器就不會在doBlockA函數中對runBlockA的調用作內聯優化處理。
咱們來看看編譯器作的工做內容:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
#import __attribute__((noinline))
void runBlockA(struct Block_layout *block) {
block->invoke();
}
void block_invoke(struct Block_layout *block) {
// Empty block function
}
void doBlockA() {
struct Block_descriptor descriptor;
descriptor->reserved = 0;
descriptor->size = 20;
descriptor->copy = NULL;
descriptor->dispose = NULL;
struct Block_layout block;
block->isa = _NSConcreteGlobalBlock;
block->flags = 1342177280;
block->reserved = 0;
block->invoke = block_invoke;
block->descriptor = descriptor;
runBlockA(&block);
}
|
上面的代碼結合block的數據結構定義,咱們能很容易得理解編譯器內部對block的工做內容。
3.4 copy()和dispose()
上文中提到,若是咱們想要在之後繼續使用某個block,就必需要對該block進行拷貝操做,即從棧空間複製到堆空間。因此拷貝操做就須要調用Block_copy()函數,block的descriptor中有一個copy()輔助函數,該函數在Block_copy()中執行,用於當block須要拷貝對象的時候,拷貝輔助函數會retain住已經拷貝的對象。
既然有有copy那麼就應該有release,與Block_copy()對應的函數是Block_release(),它的做用不言而喻,就是釋放咱們不須要再使用的block,block的descriptor中有一個dispose()輔助函數,該函數在Block_release()中執行,負責作和copy()輔助函數相反的操做,例如釋放掉全部在block中拷貝的變量等。
4.總結