block總結

3.編譯器中的block數據結構

3.1 block的數據結構定義閉包

咱們經過大師文章中的一張圖來講明:函數

block-struct.jpg

上圖這個結構是在棧中的結構,咱們來看看對應的結構體定義:優化

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.總結

相關文章
相關標籤/搜索