Block

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

__block变量是在栈空间,其forwarding指向自身,当变量从栈空间copy到堆空间时,原来栈空间的变量的_forwarding指向了新创建的变量(堆空间上),这其实就达到了从Objective C层面改变原变量的效果,这样不管__block怎么复制到堆上,还是在栈上,都可以通过(i->__forwarding->i)来访问到变量值。

参考:
深入研究Block捕获外部变量和__block实现原理

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,088评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,715评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,361评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,099评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 60,987评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,063评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,486评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,175评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,440评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,518评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,305评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,190评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,550评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,880评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,152评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,451评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,637评论 2 335

推荐阅读更多精彩内容