Blocks能夠用一句話來歸納:帶有自動變量的匿名函數。關於Blocks的語法和用法,本文不在過分贅述。而是彙集於Blocks的本質究竟是什麼?他是怎麼實現的?ios
Block其實是C語言的擴充,也就是說,Block語法源代碼是會被編譯爲普通的C語言源代碼的。經過clang能夠將其轉換爲咱們可讀代碼,例以下面代碼:程序員
int main(int argc, const char * argv[]) {
void (^blk)(void) = ^{
printf("hello world");
};
blk();
return 0;
}
複製代碼
經過clang轉換後的代碼:編程
//block實現結構體
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//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;
}
};
//block代碼塊中的實現
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello world");
}
//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)};
int main(int argc, const char * argv[]) {
//block實現
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//block調用
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
複製代碼
簡單的幾行代碼轉換後居然增長了這麼多,可是仔細看,其實並不難理解。能夠分爲兩部分:實現block、調用block。markdown
實現block
轉換後是經過下面代碼實現block的:多線程
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
複製代碼
它調用了__main_block_impl_0結構體來實現,而該結構體又是分別包含__block_impl結構體和__main_block_desc_0結構體2個成員變量框架
// impl結構體
struct __block_impl {
void *isa; // 存儲位置,_NSConcreteStackBlock、_NSConcreteGlobalBlock、_NSConcreteMallocBlock
int Flags; // 按位表示一些 block 的附加信息
int Reserved; // 保留變量
void *FuncPtr; // 函數指針,指向 Block 要執行的函數,即__main_block_func_0
};
// Desc結構體
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)};
複製代碼
再來看__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;
}
複製代碼
其中第一個參數須要傳入函數指針,第二個參數是做爲靜態全局變量初始化的__main_block_desc_0結構體實例指針,第三個參數flags有默認值0。重點看第一個參數,他其實就是block語法生成的block函數:oop
^{ printf("hello world"); };
複製代碼
通過轉換後__main_block_func_0函數指針:spa
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello world");
}
複製代碼
再來重點看__main_block_impl_0構造函數的一行代碼:線程
impl.isa = &_NSConcreteStackBlock;
複製代碼
將_NSConcreteStackBlock地址賦值給isa。咱們再回顧下objc_object的實現,其也包含isa指針。__main_block_impl_0結構體至關於基於objc_object結構體的oc類對象的幾多題。其成員變量isa經過_NSConcreteStackBlock初始化。即_NSConcreteStackBlock至關於class_t結構體實例。在將block做爲對象處理時,其類信息放置於_NSConcreteStackBlock中。
調用block
調用block就相對簡單多了。將第一步生成的block做爲參數傳入FucPtr(也即_main_block_func_0函數),就能訪問block實現位置的上下文。
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
複製代碼
總結:block其實是經過block_impl結構體實現的,而該結構體的首地址是isa,所以在objc中,block實際上就算是對象。
經過上面咱們已經理解block匿名函數的本質了,那麼帶有自動變量又是指什麼呢?先看下面這段代碼:
int main(int argc, const char * argv[]) {
int dmy = 256;
int val = 10
id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
printf("val = %d", val);
[array addObject:@"obj"];
printf("%lu", (unsigned long)[array count]);
};
blk();
return 0;
}
複製代碼
經過clang轉換後的代碼,咱們主要來看其中的不一樣之處,首先來看__main_block_impl_0結構體:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;//編譯時就自動生成了相應的變量
__strong id array; //注意這裏
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, __strong id _array, int flags=0) : val(_val) , array(_array) {
impl.isa = &_NSConcreteStackBlock;//block的isa默認是stackBlock
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
首先來看__main_block_impl_0結構體內申明的成員變量類型與自動截取的變量類型徹底相同。block表達式未使用的自動變量不會追加到結構中,例如dmy。另外,該結構體的構造函數中,加入了自動變量的初始化。 再來看看匿名函數的實現:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy 值拷貝,即 val = 10,此時的a與傳入的__cself的val並非同一個
printf("val = %d", val);
__strong id array = __cself->array; // bound by copy
[array addObject:@"obj"];
printf("%lu", (unsigned long)[array count]);
}
複製代碼
同時,轉換後的代碼裏還多了下面這些代碼,他們是用來幹什麼的?咱們暫且不表,將在__block說明符章節中作詳細說明:
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src)
{
_Block_object_assign((void*)&dst->array,
(void*)src->array,
3/*BLOCK_FIELD_IS_OBJECT*/);
}
static void __main_block_dispose_0(struct __main_block_impl_0*src)
{
_Block_object_dispose((void*)src->array,
3/*BLOCK_FIELD_IS_OBJECT*/);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);//注意這裏
void (*dispose)(struct __main_block_impl_0*);//注意這裏
}
__main_block_desc_0_DATA =
{
0,
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
複製代碼
經過以上代碼咱們能夠知道:
前面咱們說到block也是oc對象,按照isa對應的class_t信息來分類,block能夠分爲如下三種:
經過命名咱們能夠猜想出,他們對應的block對象分別存儲在全局、棧、堆上
_NSConcreteGlobalBlock
block是_NSConcreteGlobalBlock的狀況有如下兩種:
void (^glo_blk)(void) = ^{
NSLog(@"global");
};
int main(int argc, const char * argv[]) {
glo_blk();
NSLog(@"%@",[glo_blk class]);
}
複製代碼
int glo_a = 1;
static int sglo_b =2;
int main(int argc, const char * argv[]) {
void (^glo_blk1)(void) = ^{//沒有使用任何外部變量
NSLog(@"glo_blk1");
};
glo_blk1();
NSLog(@"glo_blk1 : %@",[glo_blk1 class]);
static int c = 3;
void(^glo_blk2)(void) = ^() {//只用到了靜態變量、全局變量、靜態全局變量
NSLog(@"glo_a = %d,sglo_b = %d,c = %d",glo_a,sglo_b,c);
};
glo_blk2();
NSLog(@"glo_blk2 : %@",[glo_blk2 class]);
}
複製代碼
_NSConcreteStackBlock和_NSConcreteMallocBlock
ARC有效時,如下幾種狀況,編譯器會進行判斷,自動將棧上的Block複製到堆上:
例以下面這段代碼:
int num = 10;
//輸出_NSConcreteStackBlock
NSLog(@"%@",[^{
NSLog(@"%d",num);
} class]);
void (^block)(void) = ^{
NSLog(@"%d",num);
};
//輸出_NSConcreteMallocBlock
NSLog(@"%@",[block class]);
複製代碼
除此以外,都推薦使用block的copy實例方法把block複製到堆上。例以下面這個例子:
id getBlockArray()
{
int val = 10;
return [[NSArray alloc] initWithObjects:
^{NSLog(@"blk0:%d", val);},
^{NSLog(@"blk1:%d", val);}, nil];
}
int main(int argc, char * argv[]) {
id obj = getBlockArray();
void (^blk)(void) = [obj objectAtIndex:1];
blk();
return 0;
}
複製代碼
運行程序崩潰,由於NSArray內的block類型爲_NSConcreteStackBlock,getBlockArray函數執行完成後,就被自動釋放廢棄了,再執行[obj objectAtIndex:1]時,就發生異常。
爲了解決上述問題,經過手動copy複製到堆上便可:
id getBlockArray()
{
int val = 10;
return [[NSArray alloc] initWithObjects:
[^{NSLog(@"blk0:%d", val) ;} copy],
[^{NSLog(@"blk1:%d", val);} copy], nil];
}
複製代碼
再回到截獲自動變量值的例子,假如咱們在block中試圖改變自動變量val。
int main(int argc, const char * argv[]) {
NSMutableArray *array = nil;
int val = 10
void (^blk)(void) = ^{
val = 1;
array = [NSMutableArray array];
printf("val = %d", val);
};
blk();
return 0;
}
複製代碼
結果編譯器報錯。由於不能改寫被截獲的自動變量的值,我的猜想,若是在block內修改自動變量的值是可行的,那麼修改的應該是結構體內的臨時變量,與自動變量互不影響。這很容易引發開發者犯錯,爲了不這種狀況,編譯器不容許在block中修改自動變量的值,不然報錯。
爲了解決這個問題有兩種方法。第一種,將截獲的自動變量改寫成下列類型:
靜態全局變量、全局變量這兩種外部變量,由於其做用域是全局的,在block內能夠直接訪問。因此不須要截獲。 靜態局部變量自己在block語法的函數外,他是怎麼作到能夠修改值的,先來看下面代碼:
int main(int argc, const char * argv[]) {
statc int val = 10
void (^blk)(void) = ^{
val = 1;
};
blk();
return 0;
}
複製代碼
轉化後:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val;//編譯時就自動生成了相應的變量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *static_val, int flags=0) : static_val(_static_val) {
impl.isa = &_NSConcreteStackBlock;//block的isa默認是stackBlock
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
複製代碼
可是實際使用中,咱們不多采用這種方案。由於block中能夠存放超過變量做用域的自動變量,而當使用超過做用域的靜態局部變量時,沒法經過指針訪問。
爲了解決此類問題,咱們採用第二種方案"__block說明符",他又是怎麼實現的呢?
咱們將上述代碼改成:
int main(int argc, const char * argv[]) {
__block int val = 10
void (^blk)(void) = ^{
val = 1;
printf("val = %d", val);
};
blk();
return 0;
}
複製代碼
轉換後的代碼:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_i_0 *__forwarding; // 注意這裏!!!!!
int __flags;
int __size;
int val;
};
/ * Block結構體 */
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // 注意這裏!!!!!
構造函數
__main_block_impl_0(void *fp,
struct __main_block_desc_0 *desc,
__Block_byref_val_0 *_val,
int flags=0) : val(_val->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
/ * Block方法 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // 注意這裏!!!!!
(val->__forwarding->val) = 1;// 注意這裏!!!!!
}
//捕獲的變量的copy和release
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
//block的描述結構體
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(int argc, const char * argv[]) {
/ * __block int val = 0; 轉換後代碼*/
__Block_byref_val_0 val = {
0, //__isa
(__Block_byref_val_0 *)&val, // __forwarding 注意這裏!!!!
0, // flag
sizeof(__Block_byref_val_0), // size
10 // 變量i
};
blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DAYA, &val, 0x22000000);
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
複製代碼
只是在自動變量上附加了__block說明符,源代碼就急劇增長。咱們先來看看__block變量val是怎麼轉換的?
/ * __block int val = 0; 轉換後代碼*/
__Block_byref_val_0 val = {
0, //__isa
(__Block_byref_val_0 *)&val, // __forwarding 注意這裏!!!!
0, // flag
sizeof(__Block_byref_val_0), // size
10 // 變量i
};
複製代碼
咱們發現他居然變爲告終構體實例,用__block修飾的變量變成了__Block_byref_val_0結構體類型,其定義以下:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_i_0 *__forwarding; // 注意這裏!!!!!
int __flags;
int __size;
int val;
};
複製代碼
經過block結構體的初始化,咱們能夠看出,block捕獲的實際是__Block_byref_val_0結構體的地址:
blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DAYA, &val, 0x22000000);
複製代碼
再來看看__block變量的賦值代碼又是如何實現的?
//^{val = 1;}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // 注意這裏!!!!!
(val->__forwarding->val) = 1;// 注意這裏!!!!!
}
複製代碼
那麼問題來了,他是如何解決局部自動變量超出做用域後,還能正常使用的問題?爲何要設計__forwarding?
捕獲對象類型的變量和使用__block 修飾自動變量時,都在clang轉換的代碼中,看到了這樣兩個函數。簡單來講他們都是用來作block結構體變量的複製和釋放的。
//捕獲的變量的copy和release
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
複製代碼
以__block修飾的自動變量具體,棧block經過copy複製到了了堆上。此時,block使用到的__block變量也會被複制到堆上並被block持有。若是是多個block使用了同一個__block變量,那麼,有多少個block被複制到堆上,堆上的__block變量就被多少個block持有。當__block變量沒有被任何block持有時(block被廢棄了),它就會被釋放。
棧上__block變量被複制到堆上後,會將成員變量__forwarding指針從指向本身換成指向堆上的__block,而堆上__block的__forwarding纔是指向本身。
這樣,無論__block變量是在棧上仍是在堆上,均可以經過__forwarding來訪問到變量值。
總結:
最後,簡單提下block循環引用,其產生循環引用的原理根普通循環引用同樣,解決的辦法也同樣:
不在過多介紹。
參考文章:
Objective-c高級編程-ios與OS X多線程和內存管理
iOS Block原理探究以及循環引用的問題