文章目录
前言
先前看了小蓝书上的block,发现不甚了解,现重新学习block
一、什么是Blocks
Blocks 是 C 语 言 的 扩 充 功 能 。 可 以 用 一 句 话 来 表 示 Blocks 的 扩 充 功 能 : 带 有 自 动 变 量 (局 部变量)的匿名函数。
我们把这句话拆分开来理解
自动变量:
也就是局部变量
匿名函数:
即不带名称的函数
二、Blocks模式
1.Block语法
因为我们知道Block是带有自动变量的匿名函数
其与C函数只有两点不同
- 没有函数名
- 带有^
^记号叫做插入记号,这样可以让我们在项目中方便查找
接下来我们将一下Block的范式
例子:
^int (int count) {
return count + 1;
}
同时Block还可以进行省略
如果参数列表没有参数或是返回值类型为void,那么这两个地方都可以被省略
例子:
^void (void) {
printf("1");
}
可以被省略为
^{
printf("1");
}
2.Block类型变量
上一部分我们讲到了Block语法,我们这一部分将一下Block类型变量
在定义C 语言函数时,就可以将所定义函数的地址赋值给函数指针 类型变量中。
同样的,我们可以将Block语法赋值给声明为Block类型的变量中
Block类型变量实例如下:
int (^blk) (int)
声明Block 类型变量仅仅是将声明函数指针类型 变量的 “*” 变为“^”。
下面我们将Block赋值给Block类型变量
int (^blk) (int) = ^int (int count) {
return count + 1;
}
同样Block类型变量也可以作为正常变量进行使用,如进行参数传递
可以用 typedef简化他
这样^blk_t
就变成了一个block数据类型
我们可以像使用函数一样使用block
3.截获自动变量值
我们先来看一段代码
int val = 111;
char *fmt = "val = %d\n";
void (^blk)(void) = ^ {
printf(fmt, val);
};
blk();
val = 222;
fmt = "val2 = %d";
blk();
可以看到我们两次调用blk,使用的自动变量都是第一次保存自动变量的瞬间值。这里的原因其实是因为在第一次执行blk时,自动变量被加入到了blk的结构体中
这里的具体实现在后面的源代码会进行讲解
4.__block说明符
我们在上面截获自动变量时block保存了执行时的自动变量的瞬间值,保存后就无法改写该值
但是当我们使用__block修饰时就可以进行改写
5.截获的自动变量
我们截获自动变量后不能对变量进行赋值,但是可以对变量进行使用
如上会产生错误
但是使用Array就不会发生错误
如果我们要修改截获的自动变量就要加上__block修饰符
另外不能再block中使用c语言数组,因为截获自动变量的方法并没有实现对c语言的捕获
三、Blocks的实现
1.Block的实质
要深入了解Block的实质,我们就必须看他的源码
我们在终端输入命令来查看其源码
int main()
{
void (^blk)(void) = ^{printf("Block\n");};
blk();
return 0;
}
上面代码转换为源码就变为了
//block变量结构体
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;
__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\n");
}
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()
{
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 void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("block\n");
}
我们逐步分析:
__main_block_func_0
是函数的名称。这个名称是由编译器自动生成的,与 Block 的创建有关。
struct __main_block_impl_0 *__cself
是函数的参数。这个参数是一个指向 __main_block_impl_0
结构体的指针。__main_block_impl_0
是 Block
对象的实现结构体,包含了 Block 的一些元数据和状态信息。
__cself
是一个惯用的名称,表示 “current self”,即当前 Block 实例。
__main_block_impl_0
__cself
是 __main_block_impl_0
结构体的指针,该结构声明如下:
//去除构造函数
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
};
我们这里理解一下Block对象的实现结构体:他是用于表示Block对象的一种数据结构,封装了Block的元数据、捕获变量以及执行代码等信息
__block_impl
首先看他的第一个成员变量__block_impl
,他也是一个结构体,他封装了一些Block的核心元素以及信息
struct __block_impl {
void *isa; //如同对象类型的Class isa,将Block作为Objc对象是,关于该对象类的信息放置于isa指向的对象中
int Flags; //某些标志
int Reserved; //保留区域
void *FuncPtr; //函数指针
}
我们来解释一下这些参数:
isa
:我们知道Block其实质上也是一个对象,我们会在运行时使用isa指针指向对象所属的类别,但是对于Block,其会根据Block对象内存的分配位置分别指向不同位置:
栈(_NSConcreteStackBlock)与堆(_NSConcreteMallocBlock)
Flags
:这是一组标志位,用于存储Block对象的一些特性和状态信息。例如,有一个标志位表示Block对象是否被复制到堆上,另一个标志位表示Block是否使用了C++捕获变量。
Reserved
:这是一个保留区域,目前并没有被使用。可能是为了对齐或者将来的扩展而预留的空间。
void *FuncPtr
:是一个函数指针,指向Block的内部实现代码,当Block执行时,会调用这个函数执行block内部的代码
__main_block_desc_0
我们再来看一下__main_block_desc_0
这个结构体
其声明如下:
static struct __main_block_desc_0 {
size_t reserved; //保留区域
size_t Block_size; //Block的大小
};
下面我们分析一下这段代码的源代码:
void (^blk)(void) = ^{printf("Block\n");};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
总结下来就是通过生成一个Block对象的结构体实例,将其地址赋值给__main_block_impl_0
结构体的指针类型变量blk
分开来看就清晰多了
struct __mian_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __mian_block_impl_0 *blk = &tmp;
分析一下block实例的构造函数:__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)
__main_block_impl_0((void*)__main_block_func_0,&__main_block_desc_0_DATA);
:
a. __main_block_impl_0
是一个结构体,这里调用它的构造函数(或类似的初始化函数),并传入两个参数:
b. 第一个参数 ((void *)__main_block_func_0)
是一个类型转换,将 __main_block_func_0
的地址转换为 void *。__main_block_func_0
是实现 Block 功能的函数的地址。
c. 第二个参数 &__main_block_desc_0_DATA
是指向另一个结构体(描述符),__main_block_desc_0_DATA
,的地址。这个描述符通常包含有关 Block 的一些元数据,如其大小等。
Block对象的实现结构体初始化
我们来看一下__main_block_impl_0
结构体实例是如何初始化得到的
isa = &_NSConcreterStackBlock;
Flags = 0;
Reserved = 0;
FuncPtr = __main_block_func_0;
Desc = &__main_block_desc_0_DATA;
我们再来看一下函数中的这段代码:
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
我们在void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
中已经生成了一个Block的实现结构体的实例,并将其地址赋给指针变量blk
下一步我们就需要实现Block的内部代码,前面我们说到了FuncPtr
是一个函数指针,指向Block的内部实现代码,接下来我们就可以通过调用FuncPtr
来实现内部代码
去除转换部分就是
(*blk->FuncPtr)(blk)
这段代码就是简单的函数指针调用函数
这里有一个问题就是为什么Block的实质为OC对象,后面学到会进行补充
2.截获自动变量值
本部分主要讲解源代码如何截获自动变量值
int main()
{
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{printf(fmt,val);};
blk();
return 0;
}
源代码如下:
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;
const char *fmt;
int val;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt,val);}
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 dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
return 0;
}
可以看到在Block语法表达式中使用的自动变量被追加到了Block的实现结构体__main_block_impl_0
中, 同时声明的成员变量类型与自动变量类型完全相同
这里需要注意:
Block语法表达式中没有使用的自动变量不会被追加,Block自动变量的截获只针对Block中使用的自动变量
下面再来看一下Block结构体实例的构造函数与先前有什么不同
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags=0) : fmt(_fmt), val(_val);
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fmt, val));
再来看一下Block实例的内部代码实现: ^{printf(fmt,val);};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const char *fmt = __cself->fmt; // bound by copy
int val = __cself->val; // bound by copy
printf(fmt,val);
}
可以看到在Block的内部函数实现代码中使用的变量是Block对象后的实现结构体的成员变量,也就是__cself->fmt与__cself->val
这也说明这些变量在执行Block内部函数表达式前就被定义,因此源代码表达式不用改动就可以使用自动变量
同时这也说明了Block获取的是自动变量的瞬间值
3.__block说明符
我们在先前说过Block截获自动变量后无法再修改自动变量,那么如何才能再Block内部实现修改保存的自动变量的值呢?
在__block还没出现之前可以通过c语言的一些特性来对自动变量进行修改:
1、静态变量
2、静态全局变量
3、全局变量
下面来看这段代码:
int global_val = 1;
static int static_global_val = 2;
int main()
{
static int static_val = 3;
void (^blk)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
};
return 0;
}
源代码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
int global_val = 1;
static int static_global_val = 2;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *static_val; //静态变量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()
{
static int static_val = 3;
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_val)); //静态变量static_val的指针传递给__main_block_impl_0结构体的构造函数
return 0;
}
我们看到,对于静态全局变量static_global_val
和全局变量global_val
,在转换后的函数中直接使用。但是static_val
的指针被加到了Block对象的实现结构体中。在使用static_val
时需要通过其指针对其进行访问。
这样一来我们就可以实现在Block实现代码内部修改变量
但是这样就引出了一个新的问题:
为什么自动变量不和静态变量一样通过指针作为成员变量保存在
__main_block_impl_0
中,这样不也可以对自动变量进行修改了吗?
在Block中我们可以存有超出其变量作用域的自动变量,但是我们常常在超出变量作用域的时候使用Block,因为超出变量作用域时变量已经被废弃,如果我们再次通过指针访问变量那么将会发生错误
解决Block中不能保存值的第二个方法是使用__block
说明符
int main()
{
__block int val = 3; //__block修改val自动变量
void (^blk)(void) = ^{
val *= 1; //修改自动变量的值
};
return 0;
}
源代码:
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
·
·
//新增的结构体
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()
{
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 3};
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代码含量剧增
我们发现__block使val变成了一个结构体
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
可以看到初始化如下
__Block_byref_val_0 val = {
0, //__isa
&val, //__forwarding指向自己
0, //__flags = 0
sizeof(__Block_byref_val_0), //__size 为自身__Block_byref_val_0的大小
3, //自动变量的值
}
再来看一下源代码中是如何给变量进行修改的
这个过程更加复杂,__main_block_impl_0
结构体持有了指向__block
变量的__Block_byref_val_0
的结构体实例指针
同时__Block_byref_val_0
结构体中的成员变量__forwarding
持有指向该实例自身的指针,然后我们可以通过__forwarding
(__forwarding
持有指向该实例自身的指针)来访问原成员变量val
为什么需要__forwarding
呢,后面的部分会讲到
另外不将__Block_byref_val_0
结构体添加到__main_block_impl_0
是为了在多个Block中都可以使用__block变量
4.Block存储域
通过我们前面的源码可以得知,Block转换为Block结构体类型变量__main_block_impl_0
,__block转换为block变量的结构体类型变量__Block_byref_val_0
所谓结构体类型的自动变量,就是栈上生成的结构体的实例
另外我们知道Block也是OC对象,同时我们知道他也有isa指针,他的isa指针表明其当前所存储的区域
然后我们来梳理上节遗留的问题
- Block超出变量作用域可存在的原因
- block变量用结构体成员变量_ _ forwarding存在的原因
当Block超出其变量作用域,该Block会被废弃,配置在栈上的__block
也会被废弃
Blocks提供讲Block 和_ _block 变量从栈上复制到堆上的方法来解决这个问题,这 样 即 使Block语 法 记 述 的 变 量 作 用 域 结 束 , 堆 上的Block还可以继续存在
然后堆上的Block的isa指针就会指向_NSConcreteMallocBlock类
而__block
变量中使用结构体成员变量__forwarding
可以实现无论__block变量配置在栈上还是堆上都可以被正确访问。这也就是__forwarding成员变量存在的原因。
这里引用一下上面出现过的代码:
(val->__forwarding->val) *= 1;
我们在上面讲过在使用__block
变量时,我们可以通过__forwarding
访问原成员变量val
当block从栈上复制到堆上的时候,__forwarding
的指向就由val变为了堆上的block结构体实例。
ps: 栈上和堆上同时保持__block变量实例,但是访问和修改值则是在堆上
我们这里分析一下Blocks提供的复制方法究竟是什么
在ARC有效的时候,编译器会自行判断并自动生成将Block从栈上复制到堆上的代码
该源代码返回配置在栈上的Block,当函数返回时变量作用域结束,而后栈上的Block被废弃。但ARC会自动帮我们处理这个问题
在ARC有效时,blk_t tmp实际上带有__strong修饰符
通过objc_retainBlock可知函数实际上是Block_copy
在这之间发生的情况书上讲解的很清楚
将Block作为函数值返回时,编译器就会自动生成复制到堆上的到吗
在大多数情况下编译器会自行判断,当然也有一些情况需要我们手动复制:
- 向方法或者函数的参数传递Block时。
但是在方法或者函数里面适当的复制了传递的参数时,那么就不需要手动复制 - GCD的API不需要
- cocoa框架方法且方法名包含usingBlock
同样Block类型变量也可以使用copy方法进行复制
blkCopy = [blk copy];
5.__block变量存储域
这一部分我们讲一下当__block变量从栈上被复制到堆的时候其会受什么影响
当Block被从栈复制到堆上时,__block
变量也会一起被复制到堆上并且被Block持有
如果多个Block使用同一个__block变量,那么复制到堆上的Block都会持有__block对象
如果堆上的Block被废弃,那么其所使用的__block变量也会被释放
由此我们可以知道__block的思考方式与OC引用计数管理完全相同
同时通过Block从栈上复制到堆上之后,原来的栈上的__block变量的__forwarding指针从指向自身变味指向堆上的__block结构体,由此不管__block变量配置在栈上还是堆上都可以顺利访问同一个__block变量
6.截获对象
首先来看一段源代码
blk_t blk;
int main()
{
id array = [[NSMutableArray alloc] init];
blk = [^(id obj) {
[array addObject:obj];
} copy];
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
blk([[NSObject alloc] init]);
}
这段输出意味着array超出其变量作用域而存在
同时通过解析后的源码可知Araay被夹到了Block的实现结构体中
同时OC为了准确把握Block从栈上复制到堆上以及堆上的Block的废弃的时机,在Block结构体中含有__strong与__weak修饰符的变量也会被恰当的初始化以及废弃。为此在__main_block_desc_0
结构体中增加了成员变量copy与dispose作为指针赋值给函数__main_block_copy_0
以及 __main_block_dispose_0
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_object_assign
函数调用,相当于retain实例方法的函数。将对象赋值在对象类型的结构体成员变量中。有retain方法,肯定有release方法。__main_block_dispose_0
函数调用__main_block_dispose_0
函数释放赋值在Block中的结构体成员变量arrar中的对象。
同时我们了解一下调用这两个函数的时机
那么什么时候栈上的block会复制到堆上呢
Block 被作为函数返回值时
如果函数返回一个栈上分配的 Block,那么在函数返回后,栈帧会被销毁,Block 就会失效。为了避免这种情况,编译器会自动将返回的 Block 复制到堆上。
int (^makeIncrementer)(int)) {
int value = 0;
int (^incrementer)(void) = ^{
value++;
return value;
};
return incrementer; // 编译器会自动将 incrementer 复制到堆上
}
int (^increment)(void) = makeIncrementer(0);
NSLog(@"%d", increment()); // 输出 1
NSLog(@"%d", increment()); // 输出 2
Block 被赋值给 __block 修饰的变量时
如果你将一个栈上的 Block 赋值给一个 __block 修饰的变量,编译器会自动将这个 Block 复制到堆上。__block 变量用于存储指向堆上 Block 的指针。
__block int (^blockVar)(void);
int value = 42;
blockVar = ^{
return value; // 编译器会自动将该 Block 复制到堆上
};
NSLog(@"%d", blockVar()); // 输出 42
调用 Block_copy() 函数时
你可以手动调用 Block_copy() 函数将栈上的 Block 复制到堆上。这通常用于确保 Block 在超出其定义作用域后仍然有效。
int (^blockObj)(void) = ^{
return 100;
};
blockObj = Block_copy(blockObj); // 手动将 blockObj 复制到堆上
NSLog(@"%d", blockObj()); // 输出 100
Block 被 GCD API 持有时
如果你将一个 Block 传递给 Grand Central Dispatch (GCD) API (如 dispatch_async),GCD 会自动将该 Block 复制到堆上,以确保在异步执行期间 Block 是有效的。
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
int value = 42;
dispatch_async(queue, ^{
NSLog(@"%d", value); // GCD 会自动将该 Block 复制到堆上
});
Block 访问了 __block 修饰的变量时
如果 Block 内部访问了 __block 修饰的变量,编译器会自动将该 Block 复制到堆上,以确保变量在 Block 执行时是有效的。
__block int value = 42;
int (^blockObj)(void) = ^{
value = 100; // 访问了 __block 变量,编译器会自动将该 Block 复制到堆上
return value;
};
NSLog(@"%d", blockObj()); // 输出 100
另外需要知道只有调用_ Block_copy
函数才能持有截获的附有_ _strong
修饰符的对象类型的自动变量值
- (void)myMethod {
__strong NSString *str = @"Hello";
void (^blockObj)(void) = ^{
NSLog(@"%@", str); // 默认捕获str的值复制
};
blockObj(); // 输出"Hello"
// 如果不调用_Block_copy,str在这里被释放
blockObj = _Block_copy(blockObj); // 将Block复制到堆上,并持有str的强引用
// 现在无论str何时被释放,blockObj都能正确输出"Hello"
}
7.__block变量与对象
我们知道在ARC中会给id类型变量自动加上__strong修饰符,只有使用__strong修饰符的变量才会在block从栈复制到堆时使用_Block_object_assign
来持有__block变量。如果使用__weak修饰符就当作用与结束时__block变量也会自动被释放
由此我们可以知道只有自动变量用__strong进行修饰时才会被block持有,且不随作用域结束而销毁
同时在blk被定义的时候blk就已经捕获了自动变量,而不是在调用blk时才进行捕获
blk = (blk_t)((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"));
8.Block循环引用
同时Block也会有循环引用的问题,这里就需要用__weak
,__block
,__unsafe_unretained
修饰符来避免循环引用。
这一部分后面会专门进行讲解