Block
1.Block的定義和語法
2.Block的本質和分類
3.__block的實現原理
Block的定義和語法
Block是具有自動變量(局部變量)的匿名函數
自動變量
因為局部變量的作用域不是全局的,有的時候就需要對變量的值進行保存,而Block可以捕獲變量,不需要使用全局變量或者靜態變量,更不需要聲明一個類去保存變量的值.
匿名函數
沒有名字的函數,由{}包裹起來,限制了其作用域
Block和函數指針的對比
//定義一個函數
int square(int value){
return value * value;
}
//函數指針
int (*funcptr)(int) = □
int result = (*funcptr)(10);
//Block ()可以省略
^(){
NSLog(@"匿名函數");
}();
使用函數指針,當對函數指針進行賦值的時候還是需要知道函數的名字,才能知道函數的地址.
語法
^ 返回值類型 (參數列表){函數執行體||表達式};
^ 代表這是一個Block標記,便于查找
^int (int num){
return num * num;
};
//最簡單的Block,沒有返回值,沒有參數列表可以省略
^{
printf("Block\n");
};
Block類型變量
語法:返回值類型 (^變量名) 參數列表
等同于函數指針的聲明,將*換成^
//returnType (^BlockName)(type name....)
int (^blocks)(int value) = ^(int a){
return a * a;
};
block可以用作變量,函數參數,函數返回參數,結合typedef的使用更加方便
//定義int (^) int類型的Block 名字為Add
typedef int(^Add)(int num);
//作為函數返回值
Add Test(int num){
return ^int (int a){
//這里對num變量進行了捕獲
return num + a;
};
}
//打印 15
NSLog(@"%d",Test(5)(10));
Block不允許修改局部變量的值
為什么?因為靜態變量存在于應用程序的整個生命周期,而非靜態局部變量,僅僅是存在于一個局部的上下文中。如果block執行過程中其所指向的非靜態局部變量還沒有被?;厥盏脑?,這樣執行沒有任何問題,但是絕大多數情況下,block都是延后執行的,修改已經被回收的值很可能拋出指針異常。
其實block對局部變量進行捕獲,會在block體內({}內)新定義一個變量,并進行賦值,所以在外部進行值的修改,對內部無任何影響,因為是兩個不同的變量
同樣當我們在Block內部修改外部變量的值,編譯器會報出錯,提示使用__block修飾符
__block 所起到的作用就是只要觀察到該變量被 block 所持有,就將“外部變量”在棧中的內存地址放到了堆中。進而在block內部也可以修改外部變量的值。
int num = 2;
NSLog(@"%d,%p",num,&num);
void (^block)() = ^{
NSLog(@"%d,%p",num,&num);
};
num = 3;
block();
//打印結果:
//2,0x7ffeefbff62c
//2,0x102803880
//在block捕獲之后對變量進行了改變,但是block打印的內容還是之前的值
使用__block修飾符
__block int num = 2;
NSLog(@"%d,%p",num,&num);
void (^block)() = ^{
NSLog(@"%d,%p",num,&num);
};
num = 3;
block();
//打印結果:
//2,0x7ffeefbff628
//3,0x10058ce38
//在block捕獲之后對變量進行了改變,block打印的內容也發生了改變
表面上來看,默認block捕獲變量是進行的值傳遞,使用了__block之后,傳遞的是地址,但是這種說法是不正確的,我們可以看到無論是否使用__block修飾符,在block內部和外部打印變量的地址是完全不相同的,地址的差別很大,而局部變量是存放在棧區的,可以推斷后面的變量是在堆區域的.而在ARC下,當對Block進行賦值,系統會自動的將block拷貝的堆區,下面我們介紹Block的本質和分類之后再進行剖析.
Block的本質和分類
定義一個Block,通過NSLog進行打印
void (^globalBlock)() = ^{
NSLog(@"globalBlock");
};
NSLog(@"%@",globalBlock);
打印結果:<NSGlobalBlock: 0x1000020d0>,和打印對象的結果一樣,前面是類名,后面是對象地址.
這是一個未捕獲任何變量的Block,我們可以聲明一個捕獲變量的Block來查看結果是否一致
int num = 2;
void (^block)(void) = ^{
NSLog(@"%d",num);
};
NSLog(@"%@",block);
block();
打印結果:<NSMallocBlock: 0x1004028a0> 我們知道在ARC下,只要對Block進行賦值,就會將Block復制到堆上,那么我們定義一個不賦值的Block進行查看
int a = 8;
NSLog(@"%@",^{
NSLog(@"%d",a);
});
打印結果:<NSStackBlock: 0x7ffeefbff608>
通過打印結果我們可以猜測Block的本質就是一個對象,所屬不同的類
事實勝于雄辯,我們通過編譯器指令來查看底層轉換代碼來一探究竟
clang 是 Objective-C 的編譯器前端,用它可以將 Objective-C 代碼轉換為 C/C++ 代碼,然后可以來分析 Objective-C 中的一些特性是怎么實現的。
clang -rewrite-objc main.m -o main.cpp
指定架構模式進行轉換
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc
void (^test)(void) = ^{
};
test();
轉換后的代碼,只提取關鍵部分
struct __block_impl {
//isa 指向block所屬的類 ->block的本質是一個對象
void *isa;
//標識位
int Flags;
//預留字段
int Reserved;
//函數指針
void *FuncPtr;
};
struct __main_block_impl_0 {
//block實現
struct __block_impl impl;
//block描述
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) {
}
static struct __main_block_desc_0 {
//預留字段
size_t reserved;
//大小
size_t Block_size;
}
//生成block_desc結構體對象
__main_block_desc_0_DATA = {
0,
sizeof(struct __main_block_impl_0)
};
int main(int argc, const char * argv[]) {
//函數指針 調用構造函數,傳入函數指針,block_desc對象,強轉為函數指針類型
void (*test)(void) = (
(void (*)())
&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA
)
);
//調用函數的實現,傳入test指針(實際為__main_block_impl_0類型)當做參數
((void (*)(__block_impl *))
((__block_impl *)test)->FuncPtr
)((__block_impl *)test);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
當block捕獲局部變量時
int value = 10;
void (^test)(void) = ^{
NSLog(@"%d",value);
};
test();
轉換后
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 value;
//構造函數 :后面是初始化列表 這里相當于value = _value;
//初始化類的成員有兩種方式,一是使用初始化列表,二是在構造函數體內進行賦值操作。
//初始化列表性能更好,對于內置類型,如int, float等,使用初始化類表和在構造函數體內初始化差別不是很大,但是對于類類型來說,使用初始化列表更加高效
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _value, int flags=0) : value(_value) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
//取出變量值 打印
int value = __cself->value; // bound by copy
//NSLog....
}
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[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int value = 10;
void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, value));
((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
只是block內部多了一個捕獲變量類型的成員變量.
總結:
_NSConcreteStackBlock:
只用到外部局部變量、成員屬性變量,且沒有強指針引用的block都是StackBlock。
StackBlock的生命周期由系統控制的,一旦返回之后,就被系統銷毀了。_NSConcreteMallocBlock:
有強指針引用或copy修飾的成員屬性引用的block會被復制一份到堆中成為MallocBlock,沒有強指針引用即銷毀,生命周期由程序員控制_NSConcreteGlobalBlock:
沒有用到外界變量或只用到全局變量、靜態變量的block為_NSConcreteGlobalBlock,生命周期從創建到應用程序結束。
__block的實現原理
__block int a = 0;
void (^block)(void) = ^{
a++;
NSLog(@"%d",a);
};
block();
轉換后的代碼
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//__block
struct __Block_byref_a_0 {
void *__isa;
/**重要**/
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
//變量
int a;
};
struct __main_block_impl_0 {
//block
struct __block_impl impl;
//描述變量
struct __main_block_desc_0* Desc;
//__block修飾生成的結構體
__Block_byref_a_0 *a; // by ref
//構造函數
//a 的值是 a->__forwarding
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__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_a_0 *a = __cself->a; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_cm_kv9bb84x62b8y9g8ml72bc3w0000gn_T_main_a5279b_mi_0,(a->__forwarding->a));
}
//Copy操作 第一個為目標對象 第二個參數為原來的對象
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src){
_Block_object_assign((void*)&dst->a,
(void*)src->a,
8/*BLOCK_FIELD_IS_BYREF*/);
}
//Dispose
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->a,
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*);
}
//block描述的初始化方法
__main_block_desc_0_DATA = { 0,
//大小計算,僅僅是進行了sizeof
sizeof(struct __main_block_impl_0),
__main_block_copy_0,
__main_block_dispose_0
};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ {
__AtAutoreleasePool __autoreleasepool;
//__block定義一個變量,實際上就是生成了一個__Block_byref_xx_0類型的結構體變量
//結構體有5個成員變量。第一個是isa指針,第二個是指向自身類型的__forwarding指針,第三個是一個標記flag,第四個是它的大小,第五個是變量值,名字和變量名同名。
// 傳入的參數分別是:
// isa: void *0;
// __forwarding: &a,即a結構體變量的地址
// __flags: 0
// __size: sizeof(結構體)
// a: 0,即a的值
//__forwarding指針初始化傳遞的是自己的地址
__attribute__((__blocks__(byref)))
__Block_byref_a_0 a = {
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
0};
void (*block)(void);
//Block的定義
block = ((void (*)())&__main_block_impl_0(//函數實現
(void *)__main_block_func_0,
//描述block函數函數
&__main_block_desc_0_DATA,
//__block_byref__
(__Block_byref_a_0 *)&a,570425344)
);
//a++
(a.__forwarding->a)++;
//拿到FuncPtr函數指針,FuncPtr函數有一個參數,即傳入的block 自身
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
///
棧上的Block會持有__block對象,把Block復制到堆上,堆上也會重新復制一份__block,并且該Block也會繼續持有該__block。當Block釋放的時候,__block沒有被任何對象引用,也會被釋放銷毀。
__block變量是在??臻g,其forwarding指向自身,當變量從??臻gcopy到堆空間時,原來??臻g的變量的_forwarding指向了新創建的變量(堆空間上),這其實就達到了從Objective C層面改變原變量的效果,這樣不管__block怎么復制到堆上,還是在棧上,都可以通過(i->__forwarding->i)來訪問到變量值。