1.Block的實現(xiàn)
我們在命令行下輸入
clang -rewrite-objc 源代碼文件名
就可以把含有block語法的源代碼變換為C++的源代碼。
int main(int argc, char * argv[]) {
void (^blk)(void) = ^{
printf("block");
};
blk();
return 0;
}
上面是最簡單的block
我們把它轉(zhuǎn)化為C++的源代碼:
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");
}
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, char * argv[]) {
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;
}
- 1.實現(xiàn)block
void (blk)(void) = ((void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
簡化一下:
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;
這一段源碼將_main_block_impl_0結(jié)構(gòu)體實例的指針賦值給_main_block_impl_0結(jié)構(gòu)體指針類型的變量。
看一下_main_block_impl_0結(jié)構(gòu)體實例:
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;
}
};
_main_block_impl_0結(jié)構(gòu)體的構(gòu)造函數(shù)有兩個參數(shù)侧馅,第一個參數(shù)需要傳入一個函數(shù)指針丸逸,第二個參數(shù)是作為靜態(tài)全局變量初始化的__main_block_desc_0結(jié)構(gòu)體實例指針脖律,第三個參數(shù)有值flags = 0牺弹。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("block");
}
這一部分就是相當(dāng)于block塊中的^{};也即匿名函數(shù)作為簡單的C語言函數(shù)來處理喇澡。
- 總結(jié)起來實現(xiàn)block就是聲明一個結(jié)構(gòu)體迅栅,利用結(jié)構(gòu)體的構(gòu)造函數(shù)去初始化結(jié)構(gòu)體中的成員變量,帶入?yún)?shù)從初始化之后的成員變量是這樣的:
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = _main_block_impl_func;
Desc = &_main_block_desc_0_DATA;
初始化里面最有價值的就是初始化impl.FuncPtr晴玖。
- 2 .調(diào)用block读存。
調(diào)用block的源代碼是:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
化簡一下就是:
(*blk->impl.FuncPtr)(blk);
這就是用結(jié)構(gòu)體指針來調(diào)用函數(shù),并且將blk作為參數(shù)傳入呕屎。上面的源碼就等價于:
_main_block_func_0(blk);
由此我們也可以看出block是作為參數(shù)來傳遞的让簿。
2.截獲自動變量值
變量分為四種,即局部變量秀睛,全局變量尔当,靜態(tài)變量,全局靜態(tài)變量蹂安。這里的局部變量即為自動變量椭迎。定義一個靜態(tài)變量系統(tǒng)會自動初始化,但是定義有一個自動變量系統(tǒng)不會自動幫你初始化田盈,
截獲的意思即保存該自動變量的瞬時值畜号,并且在block內(nèi)不能對該自動變量進(jìn)行修改。
看下面的代碼:
int a = 1;
static int b = 2;
int main(int argc, const char * argv[]) {
int c = 3;
static int d = 4;
NSMutableString *str = [[NSMutableString alloc]initWithString:@"hello"];
void (^blk)(void) = ^{
a++;
b++;
d++;
[str appendString:@"world"];
NSLog(@"1----------- a = %d,b = %d,c = %d,d = %d,str = %@",a,b,c,d,str);
};
a++;
b++;
c++;
d++;
str = [[NSMutableString alloc]initWithString:@"haha"];
NSLog(@"2----------- a = %d,b = %d,c = %d,d = %d,str = %@",a,b,c,d,str);
blk();
return 0;
}
執(zhí)行結(jié)果:
2----------- a = 2,b = 3,c = 4,d = 5,str = haha
1----------- a = 3,b = 4,c = 3,d = 6,str = helloworld
如果在block中對c進(jìn)行c++操作會報錯允瞧。
把它轉(zhuǎn)化為c++的源碼:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
int a = 1;
static int b = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *d;
NSMutableString *str;
int c;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_d, NSMutableString *_str, int _c, int flags=0) : d(_d), str(_str), c(_c) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *d = __cself->d; // bound by copy
NSMutableString *str = __cself->str; // bound by copy
int c = __cself->c; // bound by copy
a++;
b++;
(*d)++;
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_1);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_2,a,b,c,(*d),str);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->str, (void*)src->str, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->str, 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};
int main(int argc, const char * argv[]) {
int c = 3;
static int d = 4;
NSMutableString *str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_0);
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &d, str, c, 570425344));
a++;
b++;
c++;
d++;
str = ((NSMutableString *(*)(id, SEL, NSString *))(void *)objc_msgSend)((id)((NSMutableString *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableString"), sel_registerName("alloc")), sel_registerName("initWithString:"), (NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_3);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_4,a,b,c,d,str);
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
我們還是從block的實現(xiàn)中開始看简软,實現(xiàn)block的源碼是:
void (blk)(void) = ((void ()())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &d, str, c, 570425344));
化簡一下蛮拔,即:
struct _main_block_impl_0 tmp = _main_block_impl_0(_main_block_func_0, _main_block_desc_0_DATA, &d, str, c, 570425344);
struct _main_block_impl_0 *blk = &tmp;
我們看到_main_block_impl_0結(jié)構(gòu)體截獲了外部的三個變量,分別是&d,str,c,這里我們就可以知道痹升,結(jié)構(gòu)體截取的是靜態(tài)局部變量d的地址建炫,指針變量str,還截獲了自動變量c的值疼蛾。
然后看到__main_block_impl_0中多了三個成員變量踱卵,即
int *d;
NSMutableString*str;
int c;
然后在_main_block_impl_0這個結(jié)構(gòu)體的構(gòu)造函數(shù)中用截獲到的&d,str,c這三個值來分別初始化這三個成員變量。這樣据过,靜態(tài)局部變量d的地址惋砂,指針變量str和實值c就被_main_block_impl_0結(jié)構(gòu)體的成員變量所保存。這樣block的實現(xiàn)就全部完成了绳锅。
然后看到block的調(diào)用西饵,調(diào)用block的源碼是:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
簡化一下:
(*blk->impl.FuncPtr)(blk);
再通俗一點就是:
_main_block_func_0(blk);
我們看一下_main_block_func_0中的實現(xiàn):
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *d = __cself->d; // bound by copy
NSMutableString *str = __cself->str; // bound by copy
int c = __cself->c; // bound by copy
a++;
b++;
(*d)++;
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"), (NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_1);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_7__3g67htjj4816xmx7ltbp2ntc0000gn_T_main_150b21_mi_2,a,b,c,(*d),str);
}
剛剛我們在block的實現(xiàn)中知道,_main_block_impl_0結(jié)構(gòu)體截取了局部變量鳞芙,并且賦值給自己的成員變量保存下來【烊幔現(xiàn)在_main_block_func_0函數(shù)中的三個臨時變量指針d,指針變量str原朝,常量c的值就是由_main_block_impl_0結(jié)構(gòu)體的成員變量的值傳遞過來的驯嘱。
- 那么現(xiàn)在問題來了,為什么自動變量在block內(nèi)不能改變呢喳坠?我們梳理一下自動變量c的傳遞過程:首先是_main_block_impl_0結(jié)構(gòu)體截獲了自動變量c的值鞠评,然后把這個值賦給了自己的成員變量來保存,在調(diào)用block的時候又把這個成員變量的值傳給了_main_block_func_0中的臨時變量c,問題就在于壕鹉,這每一步傳遞都是值傳遞剃幌,所以即使block內(nèi)部臨時變量c的值改變了,真正的自動變量c的值也不會因此改變晾浴。既然如此负乡,我猜測蘋果干脆就不允許這種無意義的操作存在,因此在block內(nèi)改變自動變量的值就會報錯脊凰。
- 那為什么靜態(tài)局部變量的值在block內(nèi)能改變呢抖棘?我們還是看一下靜態(tài)局部變量d的傳遞過程。首先是_main_block_func_0結(jié)構(gòu)體截獲了靜態(tài)局部變量d的地址狸涌,然后把它賦給了結(jié)構(gòu)體的成員變量來保存切省,在調(diào)用block的時候又把這個成員變量的值即靜態(tài)局部變量d的地址傳給了臨時指針變量,這樣杈抢,_main_block_func_0中的臨時指針變量c中存放的值就是存放靜態(tài)局部變量的地址数尿,然后(*d)++就是把這個地址中的值加1,這樣靜態(tài)局部變量的值也就改變了惶楼。
- 對于NSString類型的指針變量str,str變量內(nèi)存放的是NSString類的對象的存放 地址,由于自動變量的值不能修改歼捐,所以這個str指針變量指向的地址不能改變何陆,但是!1ⅰ贷盲!這個地址內(nèi)存放的對象可以改變,所以我們就能看到_main_block_func_0函數(shù)中的執(zhí)行代碼:
((void (*)(id, SEL, NSString *))(void *)objc_msgSend)((id)str, sel_registerName("appendString:"),
這個代碼改變了str所指向的地址中的對象剥扣,但是沒有改變str所指向的地址巩剖,這樣是可以的。 - 對于全局變量钠怯。它們在一開始的時候根本就沒有被_main_block_impl_0結(jié)構(gòu)體所截獲佳魔,所以也就不存在能不能被修改了。
3.__block修飾符
因為不能改寫被截獲的自動變量的值晦炊,所以當(dāng)編譯器在變異的過程中檢查出給被截獲的自動變量的賦值操作時變產(chǎn)生編譯錯誤鞠鲜,不過這樣一來就無法在block中保存值了,很不方便断国。因此就產(chǎn)生了__block修飾符贤姆。
- __block是一種存儲域類的說明符,類似的還有typedef,extern,static,auto,register稳衬。
__block說明符類似于static霞捡,auto,register說明符薄疚,它們用于指定將變量值存儲到哪個存儲域中弄砍。例如,auto表示作為自動變量存儲在棧中输涕,static表示作為靜態(tài)變量存儲在數(shù)據(jù)區(qū)中音婶。
下面是使用__block修飾符的簡單的代碼:
int main(int argc, char * argv[]) {
__block int val = 10;
void (^blk)(void) = ^{
val = 1;
};
return 0;
}
轉(zhuǎn)化為c++的源碼:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_0 *val; // by ref
__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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
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*/);}
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, char * argv[]) {
__Block_byref_val_0 val ={
(void*)0,
(__Block_byref_val_0 *)&val,
0,
sizeof(__Block_byref_val_0),
10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
return 0;
}
可以看到增加了__block修飾符后代碼量增加了很多。
還是從main函數(shù)開始看莱坎∫率剑可以看到帶有__block修飾符的自動變量val被轉(zhuǎn)化成了__Block_byref_val_0結(jié)構(gòu)體類型的實例變量¢苁玻看一下__Block_byref_val_0結(jié)構(gòu)體:
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
其中的_forwarding是一個指向_Block_byref_val_0結(jié)構(gòu)體實例的指針碴卧。最后一個成員變量val相當(dāng)于原自動變量,這意味著該結(jié)構(gòu)體持有相當(dāng)于原自動變量的成員變量乃正。然后利用_main_block_impl_0的構(gòu)造函數(shù)來初始化自己的成員變量住册,尤其注意新增的成員變量:_Block_byref_val_0結(jié)構(gòu)體指針。
下面這段給__block變量復(fù)制的代碼又如何呢瓮具?
^{val = 1;}
該源代碼轉(zhuǎn)化如下:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_val_0 *val = __cself->val; // bound by ref
(val->__forwarding->val) = 1;
}
該源代碼首先將_main_block_impl_0結(jié)構(gòu)體的成員變量即_Block_byref_impl_0結(jié)構(gòu)體指針傳遞進(jìn)來荧飞。_Block_byref_val_0結(jié)構(gòu)體實例的成員變量_forwarding持有指向該實例自身的指針凡人。通過成員變量訪問成員變量val(成員變量val是該實例自身持有的變量,它相當(dāng)于原自動變量)叹阔。
- 總結(jié)起來拧粪,整個數(shù)據(jù)傳遞的過程就是_main_block_impl_0結(jié)構(gòu)體截獲_Block_byref_val_0結(jié)構(gòu)體實例的指針血久,然后把它傳遞給_main_block_impl_0結(jié)構(gòu)體的成員變量保存起來蹲诀。當(dāng)要調(diào)用block的時候就把_main_block_impl_0結(jié)構(gòu)體的成員變量保存的值傳遞給_main_block_func_0函數(shù)的臨時變量禁悠,這個臨時變量是_Block_byref_val_0結(jié)構(gòu)體指針,然后通過這個結(jié)構(gòu)體指針來調(diào)用_Block_byref_val_0結(jié)構(gòu)體的成員變量_forwarding睛藻,它是一個指向自身的指針启上,再通過_forwarding去調(diào)用結(jié)構(gòu)體的成員變量val,然后修改它的值店印。
通過增加__block修飾符是怎么完成修改自動變量的值的呢?
- 首先應(yīng)該注意到_main_block_impl_0結(jié)構(gòu)體截獲的是_Block_byref_val_0的實例變量val的地址冈在,因此_main_block_impl_0的結(jié)構(gòu)體中新增的成員變量即為指向該實例變量val的指針,然后通過指針去改變結(jié)構(gòu)體中成員變量的值吱窝。由于傳遞的是地址讥邻,因此當(dāng)外部調(diào)用時,自動變量val的值已經(jīng)被成功修改院峡。
4.Block存儲域和__block變量存儲域
Block存儲域
Block也是oc對象兴使,把Block當(dāng)做oc的對象來看時,Block所屬的類有三種照激,即_NSConcreteStackBlock,_NSConvreteGlobalBlock,_NSConcreteMallocBlock.
- 首先发魄,_NSConcreteStackBlock中有個stack,就是棧俩垃,即該類的對象Block設(shè)置在棧上励幼。
- _NSConvreteGlobalBlock中有g(shù)lobal,即即與全局變量一樣分配在數(shù)據(jù)區(qū)域中口柳。
- _NSConcreteMallocBlock類對象設(shè)置在由malloc函數(shù)分配的內(nèi)存塊即堆中苹粟。
我們在前面經(jīng)常看到這樣的初始化:impl.isa = &_NSConcreteStackBlock跃闹。這就說明該Block屬于_NSConcreteStackBlock類嵌削,分配在棧區(qū)。
1. _NSConvreteGlobalBlock
在以下兩種情況下的任意一種情況都是將Block分配在數(shù)據(jù)區(qū)望艺,即屬于_NSConvreteGlobalBlock類苛秕。
- Block用在定義全局變量的地方。
- Block并沒有截取自動變量的值找默。
第一種情況的示例代碼如下:
void (^blk)(void) = ^{
printf("test");
};
int main(int argc, char * argv[]) {
return 0;
}
此源代碼通過聲明全局變量blk來使用Block語法艇劫,我們轉(zhuǎn)化為c++的源代碼后會發(fā)現(xiàn)其isa指針指向_NSConvreteGlobalBlock,即該Block被分配在數(shù)據(jù)區(qū)惩激。
第二種情況:
int a = 1;
static int b = 2;
int main(int argc, char * argv[]) {
int c = 3;
static int d = 4;
void (^blk)(void) = ^{
NSLog(@"測試數(shù)據(jù):a = %d, b = %d, d = %d", a, b, d);
};
return 0;
}
Block內(nèi)使用了全局變量店煞,靜態(tài)全局變量蟹演,靜態(tài)局部變量,唯獨(dú)沒有使用自動變量浅缸,因此Block類型就是_NSConvreteGlobalBlock轨帜,該Block被分配在數(shù)據(jù)區(qū)魄咕。
2. _NSConcreteStackBlock
除了以上兩種情況會將Block分配在數(shù)據(jù)區(qū)衩椒,其他情況都是講Block分配在棧區(qū),即Block屬于_NSConcreteStackBlock類哮兰。
分配在數(shù)據(jù)區(qū)的Block毛萌,從變量作用域外也可以通過指針安全地使用。但是分配在棧區(qū)的Block喝滞,當(dāng)Block超出變量的作用域范圍時阁将,該Block就會被銷毀。為了解決這一問題右遭,Block提供了將Block和__block變量從棧區(qū)復(fù)制到堆區(qū)的操作做盅,這樣即使Block超過變量的作用域范圍,還可以繼續(xù)訪問堆上的Block窘哈。
3. _NSConcreteMallocBlock
上面說過_NSConcreteMallocBlock的出現(xiàn)是為了解決當(dāng)Block分配在棧區(qū)時吹榴,當(dāng)Block超出變量的作用域范圍時該Block就會被銷毀的問題。
在棧區(qū)的Block何時復(fù)制到堆區(qū)多數(shù)時候是由編譯器決定的滚婉。少數(shù)時候編譯器無法判斷的情況是:
向方法或函數(shù)的參數(shù)中傳遞Block時图筹。
但是如果在方法或函數(shù)中適當(dāng)?shù)膹?fù)制了傳遞過來的參數(shù),那就不必在調(diào)用該方法或函數(shù)前手動復(fù)制了让腹。以下方法或函數(shù)不用手動復(fù)制:
- Cocoa框架的方法且方法名中含有usingBlock時远剩。
- GCD的API。
- 比如說我們在使用NSArray的enumerateObjectsUsingBlock實例方法以及dispatch_async函數(shù)時就不用手動復(fù)制骇窍。相反瓜晤,在NSArray類的initWithObjects實例方法上傳遞Block時需要手動復(fù)制。
__block變量存儲域
- 若在一個Block中使用了__block對象腹纳,則當(dāng)該Block從棧復(fù)制到堆時使用的所有__block變量也必定配置在棧上痢掠。這些__block變量也全部從棧復(fù)制到堆上,此時只估,Block持有該堆上的__block對象志群。