定义
Block 是C语言的扩充功能,是一个带有自动变量(局部变量)的
匿名函数
block在c++下面的实现(Block 本质)
- oc 代码 常规
int main(int argc, const char * argv[]) {
void (^blk)(void) = ^{
NSLog(@"BlockPrint");
};
blk();
return 0;
}
- 转换成.cpp代码
//其结构为今后版本升级所需的区域以及函数指针
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//__main_block_impl_0 结构体声明如下:
struct __main_block_impl_0 {
//第一个成员变量impl,其定义在上面,有四个成员变量
struct __block_impl impl;
//第二个成员变量Desc,其定义在下面,有2个成员变量【所需的区域与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;
}
};
//参数__cself 是结构体__main_block_impl_0 的指针
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_jw_5qlq_4rj54j7dqfnvnfn_lq40000gn_T_main_c23e5d_mi_0);
}
//所需的区域与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, const char * argv[]) {
//太复杂,简化一下:
//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 结构体类型的自动变量,即栈上生成的__main_block_impl_0 结构体实例的指针,赋值给__main_block_impl_0结构体指针类型的变量blk,
//struct __main_block_impl_0 *blk = *tmp 部分的代码对应的最初的源代码
//将Block 语法生成的Block赋给Block类型变量blk,它等于将__main_block_impl_0结构体实例的指针赋值给blk。
void (^blk)(void) = ^{
NSLog(@"BlockPrint"); };
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
//函数指针调用函数
//(*blk ->impl.FuncPtr)(blk);
((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 };
解释:
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0,& __main_block_desc_0_DATA);
- 第一个参数:Block语法转换的C语言函数指针
- 第二个参数:作为静态全局变量初始化的__main_block_impl_0结构体实例指针
由此可以看出,该源代码使用Block,即__main_block_impl_0 结构体实例的大小,进行初始化
该结构体初始化如下:
struct __main_block_impl_0 {
//这一部分为__block_impl
void *isa = &_NSConcreteStackBlcok; //将Block指针赋值给Block结构体成员变量isa
int Flags = 0;
int Reserved = 0;
void *FuncPt = __main_block_func_0;
//这一部分为__main_block_desc_0
Desc = & __main_block_desc_0_DATA;
};
Block 本质上还是Objective-C的对象
- oc 代码 截取自动变量值
#import
int main(int argc, const char * argv[]) {
int dmy = 256;
int val = 10;
const char *fmt = "val = %d\n";
void (^blk)(void) = ^{
printf(fmt,val);
};
val = 2;
fmt = "there values were change val = %d\n";
blk();
return 0;
}
- 转换成.cpp代码
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 结构体中【fmt、val】
//没有使用的变量不回追加【dym】
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 argc, const char * argv[]) {
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));
val = 2;
fmt = "there values were change val = %d\n";
((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语法时,Block 语法表达式所使用的自动变量值被保存到Block的结构体实例(Block自身)中
因为在实现上不能改写被截获自动变量的值,所以当编译器在编译过程中检出被截获自动变量赋值的操作时,便产生编译错误
为了解决上述这个问题,引入全局变量、静态变量、静态全局变量
#import
int global_val = 1;
static int static_global_val = 2;
int main(int argc, const char * argv[]) {
static int static_val = 3;
void (^blk)(void) = ^{
global_val *= 1;
static_global_val *= 2;
static_val *= 3;
};
blk();
return 0;
}
- 转换成.cpp代码
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;
__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[]) {
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));
((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 };
比较,不同的地方如下:
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;
}
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;
}
使用静态变量global_val的指针对其进行访问,将静态变量global_val的指针传递给__main_block_impl_0结构体的构造函数并保存
而全局的变量并没有当作成员变量追加到__main_block_impl_0 结构体中【global_val、static_global_val】,而是直接赋值,(不是 仅仅截获自动变量的值:重新定义了一个变量,仅仅是取而已,所以不会改变)
解决Block中不能保存值这一问题,引入__block
#import
int main(int argc, const char * argv[]) {
__block int val = 1;
void (^blk)(void) = ^{
val = 2;
};
blk();
return 0;
}
struct __Block_byref_val_0 {
void *__isa;
//__forwarding持有指向该实例本身的指针
__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) = 2;
}
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, const char * argv[]) {
__attribute__((__blocks__(byref))) __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 1};
void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_val_0 *)&val, 570425344));
((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 说明说,源代码量就急剧增加
在结构体__main_block_impl_0
中多了一个成员变量 __Block_byref_val_0 *val; // by ref
,查找方法是一个结构体【结构体类型的自动变量】
//意味着该结构体持有相当于原始自动变量的成员变量
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
//源码: val = 2;
//在前面使用静态变量时候,使用了指向静态变量的指针,而现在更复杂
//使用了 结构体指针
//__main_block_impl_0 结构体实例持有指向__Block_byref_val_0 结构体实例的指针
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) = 2;
}
Block 存储区域
通过上面可知:
Block 转换为 Block的结构体类型的自动变量【
栈上生成的该结构体的实例
】
__Block 变量 转换为 __Block 变量的结构体类型的自动变量
所谓结构体类型的自动变量即 栈上生成的该结构体的实例
查看impl.isa = &_NSConcreteStackBlock;
//__Block 变量转换为__Block 变量的结构体类型的自动变量
struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int __flags;
int __size;
int val;
};
//Block 转换为Block的结构体类型的自动变量【`栈上生成的该结构体的实例`】
struct __main_block_impl_0 {
//第一个成员变量impl,其定义在上面,有四个成员变量
struct __block_impl impl;
//第二个成员变量Desc,其定义在下面,有2个成员变量【所需的区域与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 | 栈上Block的结构体实例 |
__ Block变量 | 栈上__Block变量的结构体实例 |
- Block的类
类 | 设置对象的存储区域 |
---|---|
_NSConcreteStackBlock | 栈 |
_NSConcreteGlobalBlock | 程序的数据区域(.data 区) |
_NSConcreteMallocBlock | 堆 |
具体分析:
- o c 代码全局的block
#import
//全局的block
void (^blk)(void) = ^{
printf("Global Block\n");
};
int main(int argc, const char * argv[]) {
return 0;
}
- .cpp 代码如下
struct __blk_block_impl_0 {
struct __block_impl impl;
struct __blk_block_desc_0* Desc;
__blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
//*******************
impl.isa = &_NSConcreteGlobalBlock;
//*******************
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blk_block_func_0(struct __blk_block_impl_0 *__cself) {
printf("Global Block\n");
}
static struct __blk_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blk_block_desc_0_DATA = { 0, sizeof(struct __blk_block_impl_0)};
static __blk_block_impl_0 __global_blk_block_impl_0((void *)__blk_block_func_0, &__blk_block_desc_0_DATA);
void (*blk)(void) = ((void (*)())&__global_blk_block_impl_0);
int main(int argc, const char * argv[]) {
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
通过对比发现, 该Block的类为_NSConcreteGlobalBlock 类。此处的Block 即该Block用于结构体实例设置在程序的数据区域中。如下:
//*******************
impl.isa = &_NSConcreteGlobalBlock;
//*******************
1)因为使用的是全局变量,所以,不能使用自动变量(局部变量),所以
不存在对自动变量进行截获
2)由此Block用结构体实例的内容不依赖执行时的状态【第一点所述】,所以整个程序只需要一个实例。因此,将Block用结构体实例设置在与全局变量相同的数据区域中即可
- 继续往下看
截获自动变量、不截获自动变量
//全局的block
typedef int(^blk_t)(int);
int main(int argc, const char * argv[]) {
//截获自动变量的值都不同
for (int rate = 0; rate < 10; ++rate) {
blk_t blk = ^(int count) {
return rate * count;
};
}
//截获自动变量的值都相同
for (int rate = 0; rate < 10; ++rate) {
blk_t blk = ^(int count) {
return count * rate;
};
}
return 0;
}
他们的.cpp 代码都一样【 impl.isa = &_NSConcreteStackBlock;
】
typedef int(*blk_t)(int);
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int rate;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _rate, int flags=0) : rate(_rate) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
对比发现
使用全局的Block,只要不截获自动变量,就可以将Block用结构体实例设置在程序的
数据区域
使用全局的Block语法时,Block语法的表达式中不使用应截获的自动变量
上面分析了 栈
和 数据区域
的情况,那么 堆
呢?
我们知道,栈是不需要手动管理内存的,会随着作用域的结束而结束,栈上的Block也一样,当其所属的变量作用域结束,该Block就被废弃,同样的,__block 变量也就被废弃
正因为如此,Block提供了一个方法解决这个问题:把 Block 从栈复制到堆
1)将Block作为函数的返回值返回时,编译器会自动生成复制到堆上
2)执行copy方法
__block 变量存储域
block 从栈复制到堆时对__block变量产生的影响
__block变量的配置存储域 | block 从栈复制到堆时的影响 |
---|---|
栈 | 从栈复制到堆并被Block持有 |
堆 | 被Block持有 |
这个内存管理与OC的引用计数方式完全相同:使用__block 变量的Block 持有__block变量,如果Block被废弃,它所持有的__block变量也就被释放
利用copy方法复制使用了__block变量的Block语法。Block 和__block变量两者均时是从栈复制到堆
截获对象
先查看代码
typedef void(^blk_t)(id obj);
- (void)getBlockArray
{
blk_t blk;
{
NSMutableArray *array = [[NSMutableArray alloc] init];
blk = [^(id obj) {
[array addObject:obj];
NSLog(@"array count = %ld",array.count);
} copy];
}
blk([NSObject new]);
blk([NSObject new]);
blk([NSObject new]);
}
输出如下:
2019-01-14 22:40:53.934087+0800 BlockDemo[3764:137369] array count = 1
2019-01-14 22:40:53.934265+0800 BlockDemo[3764:137369] array count = 2
2019-01-14 22:40:53.934361+0800 BlockDemo[3764:137369] array count = 3
为社么会出现如下的结果呢?
数组array 本应该在作用域结束的同时,变量array 被销毁,但是为什么代码却能正常运行,为什么呢?
通过.cpp 源码来研究
typedef void(*blk_t)(id obj);
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 obj) {
NSMutableArray *array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_jw_5qlq_4rj54j7dqfnvnfn_lq40000gn_T_main_c0f542_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[]) {
blk_t blk;
{
NSMutableArray *array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
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"));
}
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
发现 结构体__main_block_impl_0 中 NSMutableArray *array;
, 成员变量使用__strong 修饰的【NSMutableArray *array,默认就是__strong】
在看源码中的
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*/);}
因为结构体__main_block_impl_0 中 NSMutableArray *array;
, 成员变量使用__strong 修饰的,所以需要恰当管理变量array对象,所以引入 函数__main_block_copy_0
【相当于retain】和 __main_block_dispose_0
【相当于release】,使用__strong 修饰的变量会被Block retain,随着Block的释放而释放
由此可知: Block使用的赋值给附有__strong修饰符的自动变量的对象和复制到堆上的__block变量由于被堆上的Block所持有,因而可超出其变量作用域而存在
简单的说:如果block 中使用附有__strong 修饰符的对象类型自动变量,,如果Block语法复制在成员变量blk中,那么当block从栈复制到堆时,该对象为Block所持有,这样容易引起循环引用
继续看下面两个方法,打印的结果 block都在堆上
(void)test02 {
//需要MRC模式 [-fno-objc-arc]
void(^testTwoBlock)(void) = ^(){
};
NSLog(@"testTwoBlock=%@",testTwoBlock);
//控制台输出
//2018-08-09 15:27:50.244199+0800 BlockDemo[44164:838559] testTwoBlock=<__NSMallocBlock__: 0x60000004b8b0> [ARC 环境下]
//2018-08-09 15:32:35.558669+0800 BlockDemo[44738:861130] testTwoBlock=<__NSStackBlock__: 0x7ffee3239a80> [MRC 环境下]
//栈区block,函数调用完毕就会销毁
}
- (void)test03 {
//NSMallocBlock:保存在堆中的block,此类型blcok是用copy修饰出来的block,它会随着对象的销毁而销毁,只要对象不销毁,我们就可以调用的到在堆中的block。
self.block1 = ^(NSString *str, UIColor *color){
};
NSLog(@"block1=%@",self.block1);
//控制台输出
//2018-08-09 15:36:28.530531+0800 BlockDemo[45196:879447] block1=<__NSMallocBlock__: 0x600000250860>
//用copy修饰的不会函数调用完就结束,随对象销毁才销毁,这种是在开发中正确使用block的姿势
//block在有些情况下会造成block的循环引用
}
- 总结:什么时候栈上的block会复制到堆上
1、调用Block的copy方法
2、Block作为函数的返回值返回时
3、将Block赋值给 附有__strong修饰符id类型的类或者赋值给Block类型成员变量时【如上两个方法】