文章目录
- 什么是Blocks
- Blocks模式
- Block的实现
-
- Block的实质
-
- Block的底层结构
- Block的调用
- [_ cself是什么](# _cself是什么)
- [isa = &_NSConcreteStackBlock何意味](#isa = &_NSConcreteStackBlock何意味)
- 截获自动变量值
- [_ block说明符](# _block说明符)
- Block的存储域
- __block变量存储域
- 截获对象
- __block变量和对象
- Block循环引用
- 总结
什么是Blocks
Blocks是c语言的扩充功能,用一句话概括即为:"Block 是带捕获能力的匿名函数对象;可以捕获自动变量,也可以完全不捕获"
匿名函数即不带有名称的函数(c语言不允许存在这样的函数)
我们先从c语言开始看,如下声明了一个名为func的函数,我们可以在其他地方正常调用他:
c
int func(int count);
但是如下,如果我们使用函数指针来代替直接调用函数,我们似乎不需要知道函数名也可以进行使用该函数,但是实际上都会有第一步要取得相用的函数名称才可以正常调用该函数
c
int (*funptr)(int) = &func;//都的有这一步
int result = funptr(10);
NSLog(@"%d", result);
先回顾之前c语言函数中可能使用的变量
- 自动变量(局部变量)
- 函数的参数
可以在函数多次调用之间传递值
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
虽然上面这些变量的作用域不同,但是整个程序里,一个变量总保持在一个内存区域。因此,虽然多次调用函数,该变量值总能保持不变,在任何时候以任何状态调用,使用的都是同样的变量值
c
int buttonId;
void buttonCallback (int event)
{
printf ("buttonId:&d event=&d\n" , buttonId, event) ;
}
void setButtonCallbacks () {
for (int i = 0; i < BUTTON_MAX; ++i) {
buttonid = 1;
setButtonCallback (BUTTON_IDOFFSET + i, &buttonCallback) ;
}
}
这里代码的问题很明显,即回调的时候始终返回的是for循环最后的值,因为全局的buttonId只有一个,如下,我们如果把他变成在函数内的变量再进行一个参数传递就不会出现这样的问题了
c
void buttonCallback (int event, int buttonId)
{
printf ("buttonId:&d event=&d\n" , buttonId, event) ;
}
用Block实现上面的回调即:
objc
void setButtonCallbacks()
{
for (int i = 0; i < BUTTON_MAX; ++i) {
setButtonCallbackUsingBlock(BUTTON_IDOFFSET + i, ^(int event) {
printf("buttonId:&d event=%d\n", i, event);
});
}
}
Blocks模式
Block语法
例如上面的按钮回调的例子:
objc
^void (int event) {
printf("buttonId:&d event=%d\n", i, event);
}
前面回调的block实际上语法使用了省略方式,完整形式要加上void
Block语法的格式为:
^ 返回值类型 参数列表 表达式
由于它是匿名函数,所以像上面提到的,其返回值类型可以省略
如果我们不需要用到参数,那么可以仅仅保留表达式,即:
objc
^{
return 1;
}
Block类型变量
在前面我们说c语言的时候,可以把定义函数的地址赋值给函数指针类型变量之中,如:
c
int func(int count)
{
return count + 1;
}
int (*funcptr)(int) = &func;
函数func的地址就赋值给函数指针类型变量funcptr中了
同样的,Blo ck语法也可以赋值给声明为Block类型的变量中,即源代码里一旦使用Block语法就相当于生成了可赋值给Block类型变量的"值",Blocks中,由Block语法生成的值也叫Block,如:
objc
int (^blk)(int);
声明Block类型变量仅仅是将声明函数指针类型变量的"*"变为"^",这种BLock类型变量和一般的c语言变量完全相同,有以下用途
- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
函数里使用其作为参数时:
objc
void func(int (^blk)(int))
{
// 在函数中,可以调用 blk 这个 Block。
}
Block作为返回值的时候:
objc
int (^func())(int) {
return ^(int count) { return count + 1; };
}
为了简化记述方式,这里我们就要用到typedef来解决问题
objc
typedef int (^blk_t)(int);
即一个叫blk_t接受int,返回int的block类型

截获自动变量值
前面我们已经了解了Block中的匿名函数的意思,那"带有自动变量值"是什么呢,其实在Block中的表现就是"截获自动变量值",如:
objc
int dmy = 256;
int val = 10;
void (^blk)(void) = ^ {
NSLog(@"%d %d", val, dmy);
};
dmy = 100;
val = 5;
blk();
我们运行之后打印出来的是

在这里Block里截获了之前的自动变量的值,即存这个变量时候那一瞬间的值,所以执行Block后之后改的两个变量也不会影响Block执行时候存的值,人话就是Block在创建的时候就把用到的变量拷贝了一份,所以后面再改变量影响不到他,那我们要怎么改block里的值呢,就要用到下面我们说的了
_ _block修饰符
如果我们想改Block里的自动变量,我们就给他加上_ _block修饰符就可以了,这样就可以修改我们设置在外面的局部变量,比如,我们只做以下小改动
objc
__block int dmy = 256;
__block int val = 10;
void (^blk)(void) = ^ {
NSLog(@"%d %d", val, dmy);
};
dmy = 100;
val = 5;
blk();
只在赋值那里加了个_ _block,打印的却是

截获的自动变量
如果我们直接把值赋给Block里截获的自动变量,就会产生编译错误,但是当我们截获oc对象,调用变更该对象的方法是不会产生编译错误的
objc
__block int dmy = 256;
int val = 10;
NSMutableArray* array = [@[] mutableCopy];
void (^blk)(void) = ^ {
[array addObject: @"1"];
};
dmy = 100;
val = 5;
blk();
比如这样添加是没有问题的,而向截获的变量array赋值则会产生编译错误,这里截获的变量值是NSMutableArray类的对象,用c语言描述即是截获NSMutableArray类对象的结构体实例指针。虽然赋值给截获的自动变量array的操作会产生编译错误,但使用截获的值却不会有任何问题,下面向截的自动变量赋值,所以产生编译错误

同样如果我们添加_ _block修饰符就可以正常使用,即
objc
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int dmy = 256;
int val = 10;
__block NSMutableArray* array = [@[] mutableCopy];
void (^blk)(void) = ^ {
array = [[NSMutableArray alloc] init];
};
dmy = 100;
val = 5;
blk();
}
return 0;
}
还有就是c语言里的字符串和字符数组的区别
objc
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int val = 10;
int fmt = 15;
const char text[] = "hello";
void (^blk)(void) = ^ {
printf("%c", text[2]);//Cannot refer to declaration with an array type inside block
};
fmt = 20;
blk();
}
return 0;
}
这个时候会保错,但是下面这种使用指针的就可以解决这个问题
objc
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int val = 10;
int fmt = 15;
const char* text = "hello";
void (^blk)(void) = ^ {
printf("%c", text[2]);
};
fmt = 20;
blk();
}
return 0;
}
这是因为现在的OC的block没有实现对于我们的C语言数组的自动截获,而使用指针则可以解决这个问题
Block的实现
Block的实质
先一句话总结一下Block的本质:一个"带函数指针的结构体对象"
Block实际上是作为普通的c语言源代码来处理的,而在实际编译时其无法转化成我们能理解的源代码,但是clang有帮我们转换为可读源码的功能,通过"-rewrite-objc"选项就能将含有 Block 语法的源代码变换为C++的源代码。说是C++,其实也仅是使用了struct结构,其本质是C语言源代码。
比如下面这段简单的代码:
objc
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^blk)(void) = ^{
printf("Block666");
};
blk();
}
return 0;
}
我们可以在终端进入该文件,然后将其转化为c++的源代码
clang -rewrite-objc main.m -o main.cpp
转换之后
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("Block666");
}
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;
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;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
可以看出我们在block里的内容:
c++
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block666");
}
Block的底层结构
这个_ _cself是一个block_impl的指针,指向这个结构体
c++
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
}
这个结构体的两个结构体如下,分别是
c++
struct __mian_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
这个__mian_block_impl_0是核心
isa表明他是对象
*FuncPtr函数指针
c++
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
}
desc结构体是block的描述信息,为今后升级的大小和Block的大小。主要就是用来记录
- 这个 Block 多大(Block_size)
- 以后可以扩展别的信息(reserved)
方便进行 copy、内存分配和释放时使用。同时预留字段 reserved 是为了未来扩展,保证结构体的兼容性
Block绑定代码靠的是:impl.FuncPtr = __main_block_func_0;
本质就是Block对象里存放了一个函数地址
初始化Block的构造函数如下:
c++
__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;
}
我们可以看出,调用函数的构造的一个过程:
c++
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
去掉一个转化的过程其实是
c++
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;
这里是创建一个局部变量,然后将其赋值给我们的blk,在oc里对应的就是:
objc
void (^blk)(void) = ^{
printf("Block666");
};
也就是说Block里的代码其实是被变成一个普通的c函数的
将BLock生成的Block赋值给Block类型变量,就相当于把结构体实例指针赋值给blk指针
生成实例变量的过程:
c++
__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)
第一个参数是用Block语法转换的c语言函数指针,第二个参数是作为全局变量初始化的_ _ main_block_desc_0结构体实例指针,下面是_ _main_block_desc_0结构体实例的初始化部分代码:
c++
static struct __main_block_desc_0 __main_block_desc_0_DATA = {
0, sizeof(struct __main_block_impl_0),
};
这部分的代码是根据_ _ main_block_impl_0的大小来分配内存大小的,下面来看之前的_ _ main _block_impl _是怎么进行初始化的
c++
isa = &NSConcreteStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
把一个函数指针__main_block_func_0赋值给FuncPtr这个成员变量。这个函数也就是我们之前写在Block中的函数内容:
c++
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block666");
}
Block的调用
下面再看一下用blk的部分:
c++
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
实际上代表的是
c++
(*blk->impl.FuncPtr)(blk);
((__ block_impl *)blk)->FuncPtr((__block_impl *)blk);这一句其实就是blk->FuncPtr(blk);
说人话就是调用函数指针,并把自己(Block)传进去
这就是简单使用函数指针调用函数,就是像我们刚才确认的,由Block语法转换的__main_block_func_0函数的指针被赋值成员变量FuncPtr中,同时也说明了 _ _main_block_func_0函数的参数 _ _cself指向Block值,在调用该函数的源代码中可以看出Block就是作为参数进行了传递
_ _cself是什么
c++
static void __main_block_func_0(struct __main_block_impl_0 *__cself)
其实本质__cself就等于当前Block对象自己
isa = &_NSConcreteStackBlock何意味
这里我们再说一下之前的&NSConcreteStackBlock
这里是把Block的指针赋值给isa指针,也就是说我们的Block其实是一个对象,回想一下我们的isa指针,下面展示的是类的结构体:
c++
struct MyObject {
Class isa;
int valo;
int vall;
}
这个isa是有不同类型的:
| 类型 | 说明 |
|---|---|
| _NSConcreteStackBlock | 栈上的 |
| _NSConcreteMallocBlock | 堆上的 |
| _NSConcreteGlobalBlock | 全局的 |
也就是三种不同的block后面我们会详细说明
MyObject类的实例变量val0和val1被直接声明为对象的结构体成员,"Object-c中由类生成对象",也就是说像该结构体那样"生成由该类生成的对象的结构体实例"。生成的各个对象,即由该类生成的对象的各个结构体实例,通过成员变量isa保持该类的结构体实例指针,即如下图

其实就相当于:
c++
struct __mian_block_impl_0 {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
这个结构体其实就是一个对象基于objc_object的对象
所以最后更准确一点说其实Block相当于一个OC对象+一个保留函数实现的函数指针(FuncPtr)
截获自动变量值
现在我们通过上面所说的截获自动变量值的源代码通过clang重新编译一下
c++
truct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;
int dmy;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int _dmy, int flags=0) : val(_val), dmy(_dmy) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
int dmy = __cself->dmy; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_tgb_wwv90_372z0ppbhy565r0000gp_T_main_67f18c_mi_0, val, dmy);
}
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 dmy = 256;
int val = 10;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val, dmy));
dmy = 100;
val = 5;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
这里我们可以显而易见看到这里已经把我们的两个自动变量添加到我们的block中了
c++
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int val;
int dmy;
}
这里要注意Block里捕获的变量仅限于我们的一个Block里要用到的一些变量
这里要注意的是我们的Block函数部分
c++
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int val = __cself->val; // bound by copy
int dmy = __cself->dmy; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_tgb_wwv90_372z0ppbhy565r0000gp_T_main_67f18c_mi_0, val, dmy);
}
也就是说我们使用打印的其实是已经保存在block里的__ cself->的拷贝的值,所以表面打印的是val和dmy,实际上已经是存进来新的block里的 __ cself->val和 __cself->dmy了
这里我们发现,我们这里使用到的成员变量,是通过Block这个结构体之前保存的一个结构来赋值的。初始化函数:
c++
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int _dmy, int flags=0) : val(_val), dmy(_dmy) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
这里我们就可以发现这段代码里面我们直接给内部的变量赋值好了
这时候我们前后对比一下就可以发现下面的这个道理:
在转换后的源代码中,截获到_ _ main_ block_impl_0 结构体实例的成员变量上的自动变量,这些变量在Block语法表达式之前被声明定义。因此,原来的源代码表达式无需改动便可使用截获的自动变量值执行
也就是说Block在定义的时候,会把他要用到的外部自动变量的值拷贝一份保存到自己的结构体成员里,所以即便后面原变量发生了改变,但是Block使用的还是之前当时保存下来的那一份值
Block 捕获自动变量,本质上是"值拷贝到 Block 对象内部",所以后面外部变量再修改,不影响 Block 里已经保存好的值。
_ _block说明符
之前关于修改block捕获的值,我们提到可以使用_ _block进行修改,但是其实还有另一种解决方法,就是使用静态变量,静态全局变量,全局变量这三个变量
c++
int global_val = 10;
static int static_global_val = 20;
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int static_val = 30;
void (^blk)(void) = ^ {
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
};
blk();
}
return 0;
}
clang编译之后就是
c++
int global_val = 10;
static int static_global_val = 20;
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;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
}
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;
static int static_val = 30;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
这里的静态全局变量和全局变量和之前还是一样,没有啥区别,但是对于静态变量还是有区别的
c++
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *static_val = __cself->static_val; // bound by copy
global_val *= 1;
static_global_val *= 2;
(*static_val) *= 3;
}
这里我们看到通过静态变量的指针传给构造函数并保存,这种方法是最简单的超出作用域使用变量的方法
这里的核心是因为Block 不能直接改的是"自动变量 auto 局部变量",能直接改的是"不在栈上按普通局部变量方式存活的变量"。因此Block执行时不需要"拷贝一份值再保存",而是可以直接访问他们原来的那块内存
接下来说一下__block修饰符的内容, _ _block作用时把制定的变量值设置到存储域中
例如:
objc
__block int dmy = 256;
__block int val = 10;
void (^blk)(void) = ^ {
NSLog(@"%d %d", val, dmy);
};
dmy = 100;
val = 5;
blk();
重新编译后:
c++
struct __Block_byref_dmy_0 {
void *__isa;
__Block_byref_dmy_0 *__forwarding;
int __flags;
int __size;
int dmy;
};
struct __Block_byref_val_1 {
void *__isa;
__Block_byref_val_1 *__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_1 *val; // by ref
__Block_byref_dmy_0 *dmy; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_1 *_val, __Block_byref_dmy_0 *_dmy, int flags=0) : val(_val->__forwarding), dmy(_dmy->__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_1 *val = __cself->val; // bound by ref
__Block_byref_dmy_0 *dmy = __cself->dmy; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_tgb_wwv90_372z0ppbhy565r0000gp_T_main_9f5169_mi_0, (val->__forwarding->val), (dmy->__forwarding->dmy));
}
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*/);_Block_object_assign((void*)&dst->dmy, (void*)src->dmy, 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_object_dispose((void*)src->dmy, 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, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_dmy_0 dmy = {(void*)0,(__Block_byref_dmy_0 *)&dmy, 0, sizeof(__Block_byref_dmy_0), 256};
__attribute__((__blocks__(byref))) __Block_byref_val_1 val = {(void*)0,(__Block_byref_val_1 *)&val, 0, sizeof(__Block_byref_val_1), 10};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_1 *)&val, (__Block_byref_dmy_0 *)&dmy, 570425344));
(dmy.__forwarding->dmy) = 100;
(val.__forwarding->val) = 5;
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
由__block修饰的代码在:
c++
struct __Block_byref_dmy_0 {
void *__isa;
__Block_byref_dmy_0 *__forwarding;
int __flags;
int __size;
int dmy;
};
struct __Block_byref_val_1 {
void *__isa;
__Block_byref_val_1 *__forwarding;
int __flags;
int __size;
int val;
};
我们可以看到,这两个变量分别变成了结构体,然后本来的值val,dmy分别变成了里面的值,
下面我们来重新看block中给变量赋值的代码:
c++
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_val_1 *val; // by ref
__Block_byref_dmy_0 *dmy; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_val_1 *_val, __Block_byref_dmy_0 *_dmy, int flags=0) : val(_val->__forwarding), dmy(_dmy->__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_1 *val = __cself->val; // bound by ref
__Block_byref_dmy_0 *dmy = __cself->dmy; // bound by ref
刚才在给block中给静态变量赋值时,用了指向该静态变量的指针
__block比之前使用指针更复杂,他是单独包装成一个 _ _Block _byref_xxx的结构体,Block里只保存了指向这个包装结构体的指针
如果是之前我们的直接一个int类型的值,就是直接被值捕获,但是当我们使用__block后,这个值就被编译器包装成了一个单独的结构体,使用之后有以下好处:Block里可以改,外面也可以看到改后的结果,多个BLock也可以共享同一个变量

这里有个注意的就是,__block里的结构体并不储存在Block的结构体里,这里也是为了前面说的让其可以在多个Block里被使用,所以只是保留一个指针
Block的存储域
之前了解到我们Block的类型是_NSConcreteStackBlock,然后同是呢,也有一些和他
类似的类:

如果我们定义一个全局变量的Block,那么他的类就是_NSConcreteGlobalBlock类,该类即用结构体实例设置在程序的数据区域里,所以用全局变量的地方不能用自动变量,所以不存在对自动变量进行截获
所以用这个Block结构体实例的内容不依赖执行时的状态,所以整个程序里只需要一个实例,所以把该Block用结构体实例设置在与全局变量相同的数据区域里就行了所以就是
由于该 Block 不捕获自动变量,其行为在编译期已确定,因此编译器会将其作为一个全局唯一的实例,直接存储在程序的数据区中,类似于全局变量,从而避免在运行时重复创建。
只有在截获自动变量的时候,Block用结构体实例截获的值才会跟据执行时的状态变化,如下源代码里,虽然用的是同一个Block语法,但是每个for循环中截获的自动变量值都不一样,如:
objc
typedef int (^blk_t)(int);
for (int rate = 0; rate < 10; ++rate) {
blk_t blk = ^(int count) { return rate * count; };
}
上面需要截获自动变量
objc
typedef int (^blk_t)(int);
for (int rate = 0; rate < 10; ++rate) {
blk_t blk = ^(int count) { return count; };
}
这里不需要截获自动变量,这里就是说如果不需要截获自动变量的话,可以把Block用结构体实例放在程序的数据区域
所以只有以下两种情况是_NSConcreteGlobalBlock(程序数据区位置的):即记录全局变量的地方有Block语法;Block语法的表达式里不用应截获的自动变量的
即只要 Block 不捕获自动变量,它就不依赖运行时栈状态,因此编译器会将其优化为 _NSConcreteGlobalBlock,放在程序的数据区作为全局唯一实例
除了上面这种情况,Block默认就是StackBlock,但是在有些情况下会变成堆上的MallocBlock
在变量作用域结束时,就可以用把block拷贝到堆上的方法来解决这个__block变量被废弃的问题

这里是不同BlockCopy的差异:

还有以下情况就要我们手动复制一下栈上的Block不然这个Block就会被释放掉,这里就要注意一个点,再给方法和函数中传递block的时候,要自己手动复制一下,除了以下的方法就不需要我们进行手动复制
- Cocoa 框架的方法且方法名中含有 usingBlock 等时
- Grand Central Dispatch 的 API
__block变量存储域
我们上面说复制的时候,有个Block用__block变量的部分,我们现在看这个图:

但是有多个Block用这种变量的时候,就可以看下面这张图:

多个Block使用__block变量时,因为最先会把所有的Block配置在栈上,所以 _ _block变量也会配置在栈上,任何一个Block从栈复制到堆上时,被复制的Block持有 _ _block变量且增加block变量的引用计数
若配置在堆上的Block被释放,那么我们这里的__block变量也会被释放

其实这就和正常的引用计数一样,引用计数为0就自动释放了,这里我们在重新认识一下我们的forwarding指针的作用,前面提到了forwording指针是为了让我们的__block变量的值可以一直被访问

截获对象
我们先来看下面这段代码:
objc
NSMutableArray *array = [NSMutableArray array];
void (^blk)(id object) = [^(id object) {
[array addObject: object];
NSLog(@"%ld", array.count);
} copy];
blk([[NSMutableArray alloc] init]);
blk([[NSMutableArray alloc] init]);
blk([[NSMutableArray alloc] init]);
输出结果是:

这里说明了这里的NSMutableArray没有被释放,说明array超出作用域存活
重新编译后:
c++
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableArray *array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself, id object) {
NSMutableArray *array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)object);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_tgb_wwv90_372z0ppbhy565r0000gp_T_main_0a5321_mi_0, ((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
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};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSMutableArray *array = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
void (*blk)(id object) = (void (*)(id))((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init")));
}
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
这里的block结构体
c++
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableArray *array;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, int flags=0) : array(_array) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
这里其实用到了__strong修饰符修饰成员变量,虽然这里省略了所有权修饰符的显示,但是从源码里的下面这两段可以看出他就是强引用对象捕获:
c++
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*/);}
- Block 从栈复制到堆时,array 不是简单 memcpy 完事
- 要调用 _Block_object_assign
- Block 销毁时,要调用 _Block_object_dispose
这就说明 array 是一个需要对象生命周期管理的捕获成员
至于没显示的原因可能是因为ARC下,对象指针默认就是__strong,NSMutableArray *array;就本质等价于NSMutableArray * _ _strong array;
在 Objective-C 中,C语言结构体不能含有附有__strong 修饰符的变量。因为编译器不知道应何时进行C语言结构体的初始化和废弃操作,不能很好地管理内存。但是 Objective-C 的运行时库能够准确把握Block 从栈复制到堆以及堆上的Block 被废弃的时机,因此 Block 用结构体中即使含有附有_ _ strong 修饰符或_ _ weak 修饰符的变量,也可以恰当地进行初始化和废弃。为此需要使用在 _ _ main_block _ dese_0结构体中增加的成员变量 copy和dispose,以及作为指针赋值给该成员变量的_main_block copy_0函数和 _ _ main_block_dispose 0函数。
由于在该源代码的Block 用结构体中,含有附有_ _ strong 修饰符的对象类型变量 array,所以需要恰当管理赋值给变量 array 的对象。因此 _ _ main_block_copy_0函数使用_Block_object_assign 函数将对象类型对象赋值给 Block 用结构体的成员变量 array 中并持有该对象
上面的copy和dispose即就是堆最后这段话的解释,这两个函数分别会在block复制到堆上和在被废弃时候进行调用

在下面这些情况Block会被复制到堆上
-
调用Block的copy实例方法时
在调用Block的copy实例方法时,若Block配置在栈上时,则该block会从栈上复制到堆上
-
Block作为函数返回值时
Block 作为函数返回值返回时、将Block 赋值给附有 _ _ strong 修饰符id类型的类或Block 类型成员变量时,编译器自动地将对象的Block作为参数并调用_Block_copy 函数,这与调用Block的 copy 实例方法的效果相同
-
将Block赋值给赋有__strong修饰符id类型的类或者Block类型成员变量时
-
在方法名中含有usingBlock的Cocoa框架方法或者Grand Central Dispatch 的API 中传递
Block
在方法名中含有 usingBlock 的 Cocoa 框架方法或 Grand CentralDispatch 的API 中传递 Block 时,在该方法或函数内部对传递过来的Block 调用 Block 的 copy 实列方法或者_Block _copy 函数。
也就是说,虽然从源代码来看,在上面这些情况下栈上的Block 被复制到堆上,但其实可归结为_Block_copy 函数被调用时Block 从栈复制到堆。有了这种构造,通过使用附有__strong 修饰符的自动变量,Block 中截获的对象就能够超出其变量作用域而存在
截获变量的结构体虽然大致相同但是还是有区别的

通过这两个内容可以区分捕获的是对象类型还是__block变量
Block里用的赋值给带有__strong修饰符的自动变量的对象和复制到堆上的 _ _block变量由于被堆上的Block持有,因而可以超出其变量作用域而存在
只有调用上面Block_copy那个函数才能截获附有__strong修饰符的对象类型的自动变量值,所以一般不调用 _Block_copy的函数时,即便截获了对象,也会随着变量作用域的结束而被废弃
所以Block里用对象类型自动变量时,除了下面这几种,推荐调用Block的copy方法
- Block作为函数返回值返回时
- 把Block赋值给类的附有__strong修饰符的id类型或者Block类型成员变量时
- 向方法名中含有 usingBlock 的Cocoa 框架方法或Grand Central Dispatch 的API 中传递
Block 时
__block变量和对象
当我们用__block来修饰对象时,先看下面的代码
objc
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSMutableArray* array = [NSMutableArray array];
void (^blk)(void) = ^ {
NSLog(@"1234");
};
blk();
}
return 0;
}
编译后:
c++
struct __Block_byref_array_0 {
void *__isa;
__Block_byref_array_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSMutableArray *array;
};
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) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_tgb_wwv90_372z0ppbhy565r0000gp_T_main_00dfab_mi_0);
}
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;
__attribute__((__blocks__(byref))) __Block_byref_array_0 array = {(void*)0,(__Block_byref_array_0 *)&array, 33554432, sizeof(__Block_byref_array_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"))};
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;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
我们可以看到,这里也出现了之前的
c++
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
block变量为附有strong修饰符的id类型或者对象类型自动变量的情况下会发生相同的过程,当__block变量从栈复制到堆时,用Block_assign函数,持有赋值给 _ _ block变量的对象,当堆上的 _ _block变量废弃时候,使用Block_dispose函数,释放赋值给 _ _block变量的对象
然后这里的步骤主要是:Block_copy(总入口)->处理 Block 成员->如果有 __ block->调用 byref copy(__Block_byref_id_object_copy)->里面再调用 _Block_object_assign(真正 retain 对象)
销毁也是同理:
Block 释放->调用 dispose ->__Block_byref_id_object_dispose-> _ Block_object_dispose(release 对象)
所以,即使对象赋值复制到堆上的附有 _ _ strong 修饰符的对象类型 _ _ block 变量中,只要_ block 变量在堆上继续存在,那么该对象就会继续处于被持有的状态。这与Block 中使用赋值给附有_strong 修饰符的对象类型自动变量的对象相同。
这里注意 _ _weak和 __block一起联合使用,他还是会在该作用域结束的时候被释放,
也就是说在 ARC 下,当 Block 从栈复制到堆时,会触发 Block_copy。如果 Block 捕获了 __ block 变量,则 runtime 会同时处理对应的 byref 结构。对于 __block 中的对象类型成员,会在 byref 的 copy/dispose 函数中,通过 _Block_object_assign 和 _Block_object_dispose 完成引用计数的管理。因此 _Block_object_assign 并不是触发复制的函数,而是 Block copy 过程中用于管理对象生命周期的辅助函数。
Block循环引用
我们在Block中经常会写出有关于循环引用的内容
objc
- (id) init {
self = [super init];
blk_ = ^(NSLog(@"self = 8@", self) ;):
return self;
}
总结出就是self对象持有一个Block,BLock又捕获了self(强引用),形成了互相持有,也就是循环引用如图:
这里是因为ARC的释放规则是:引用计数必须归零才释放,两个互相引用加着就永远不会变0
主要的解决办法是__weak变量来修饰一下:
原理直接就是把引用变弱,让其不增加引用计数
objc
id __weak tmp = self;
同时也可以用__block变量来避免循环引用
这里的断环原理是让变量可以修改,然后在合适的时候进行手动断环
objc
typedef void (^blk_t)(void);
@interface MyObject : NSObject {
blk_t blk;
}
@end
@implementation MyObject
- (id)init {
self = [super init];
__block id tmp = self;
blk = ^{
NSLog(@"self = %@", tmp);
tmp = nil;//在这里进行了断环的操作
};
return self;
}
- (void)execBlock {
blk();
}
- (void)dealloc {
NSLog(@"dealloc");
}
@end
int main() {
id o = [[MyObject alloc] init];
[o execBlock];
return 0;
}
所以说这里其实本来也是有循环引用的,所以我们必须执行上面的- (void)execBlock函数进行手动断环才可以解决循环引用的问题
也就是说我们在block里已经写了 tmp = nil,而这句代码只有执行 Block 才会生效也就如下面两个图

调用这个函数,把__block这个变量重置为nil

总结
这里Block的东西比较杂,应该后面多看多记忆