什么是block?
带有自动变了的匿名函数。
- block其实也是一个oc对象,内部有一个isa指针。
block本质
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
void(^testBlock)(void) = ^{
NSLog(@"a=%d",a);
};
testBlock();
}
return 0;
}
先通过命令行编译一下
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//// 封装了block内部执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5v_mzhg9jss4vdc16_hqrk9pfp00000gn_T_main_51326f_mi_0,a);
}
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 a = 10;
// 定义block变量
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
//调用
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);;
}
return 0;
}
先看main方法,里面有定义的block
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
这里其实就是调用了__main_block_impl_0函数并且传入了__main_block_func_0、__main_block_desc_0_DATA 参数。
__main_block_impl_0结构如下:
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;
}
};
这里有一个结构体impl、一个结构体指针Desc和自身的构造函数,自身构造函数我们先忽略。先看下__block_impl
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
这里看到有一些标志和指针,我们也看到了isa。
再看下__main_block_desc_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)};
//这里reserved 是0 , Block_size是__main_block_impl_0的空间大小。
__main_block_func_0,封装了block内部执行逻辑的函数。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_5v_mzhg9jss4vdc16_hqrk9pfp00000gn_T_main_b3d2a9_mi_0);
}
最后看下__main_block_impl_0构造函数
__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;
}
这里可以看到将一部分信息保存到了impl结构体中,desc保存在了Desc中。
1. 首先调用了__main_block_impl_0构造函数,参数(block执行逻辑函数__main_block_func_0,和block描述信息__main_block_desc_0)
2.执行逻辑函数指针fp存放在了impl.FuncPtr中
3.desc存放在了Desc中
再看下调用代码
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
去掉各种类型转换
testBlock->FuncPtr(testBlock);
testBlock是__main_block_impl_0,impl是__main_block_impl_0内部的第一个成员,所以testBlock的地址也是impl的地址,被强转成__block_impl类型,然后取到内部的FuncPtr,调用函数,参数就是自己。
大概总结一张结构关系图:
block捕获变量
int c = 30;
int main(int argc, const char * argv[]) {
@autoreleasepool {
int a = 10;
static b = 20;
void(^testBlock)(void) = ^{
NSLog(@"a is %d, b is %d, c is %d");
};
testBlock();;
}
return 0;
}
将代码编译之后,我们看下有什么变化。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
int *b;
};
我们可以看见这里多了2个成员,一个int a, 一个int *b,没有c。而且a是获取了值,b是获取了地址。
再看下构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int *_b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
这里a(_a), b(_b),其实就是把传进来的_a赋值给a, _b赋值给b。
这里我们看三种不同变量的差异:
- a是auto变量,其实是这样
auto int a = 10
,自动变量,是执行完函数之后,自动释放的局部变量,因为可能会释放,但是block还可以继续保留执行,如果继续访问a,可能会发生坏内存访问,所以需要捕获一份变量值。正是因为这样,外部修改a的值,并不会影响block内部的值,而且block内部也不能修改a的值,因为这里的a是指的block外面的局部变量a, block只是捕获了a的值,并不能修改a,只是名字相同。 - static 静态变量,不会被销毁。所以可以使用地址来继续访问。由于是指针传递,所以如果在外部修改值,block内部使用的值也会发生变化。block内部修改值,外部也会变化。
- c 全局变量,在任何地方都可以访问,所以不需要捕获。
总结一张图:
block的类型
block是OC对象,可以通过isa看一下
void (^block)(void) = ^{
NSLog(@"block");
};
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
NSLog(@"%@",[[[[[block class] superclass] superclass] superclass] superclass]);
输出
NSGlobalBlock
__NSGlobalBlock
NSBlock
NSObject
null
block是继承自NSBlock,NSBlock又继承自NSObject。block是OC对象。
block有三种类型
__NSGlobalBlock__ ( _NSConcreteGlobalBlock )
__NSStackBlock__ ( _NSConcreteStackBlock )
__NSMallocBlock__ ( _NSConcreteMallocBlock )
看下三种类型:
int age = 1;
void (^block1)(void) = ^{
NSLog(@"block1");
};
void (^block2)(void) = ^{
NSLog(@"block2:%d",age);
};
NSLog(@"%@/%@/%@",[block1 class],[block2 class],[^{
NSLog(@"block3:%d",age);
} class]);
不同类型的block的存储域:
block是怎么区分类型和存储域的?
验证需要在MRC环境下进行。(先略过,以后再说)
结论:
- 没有访问auto变量的block是__NSGlobalBlock __ ,放在数据段
- 访问了auto变量的block是__NSStackBlock __
- [__NSStackBlock __ copy]操作就变成了__NSMallocBlock __
在ARC环境下,编译器会根据情况自动将栈上的block进行一次copy操作,将block复制到堆上。
以下几种情况:
- block作为函数返回值时
- 将block赋值给__strong指针时
- block作为Cocoa API中方法名含有usingBlock的方法参数时
- block作为GCD API的方法参数时
由于时间关系,最近写的越来越仓促了。