主要介绍block的类型和底层分析
block类型
block主要由三种类型
- NSGlobalBlock:全局block,存储在全局区
void(^block)(void) = ^{
NSLog(@"LTD");
};
NSLog(@"%@", block);
当前block内没有捕获外部变量,属于全局block。
- NSMallocBlock:堆区block,因为block既是函数,也是对象
int a = 10;
void(^testBlock)(void) = ^{
NSLog(@"%d",a);
};
testBlock();
NSLog(@"%@",testBlock);
当前block会捕获外界变量,是堆区block。
- NSStackBlock:栈区block
int a = 10;
void(^__weak testBlock)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",testBlock);
如果不用__weak
修饰变量,那么该变量就是NSMallocBlock
。但是通过__weak
修饰变量后,那该变量类型就是栈区block
。
总结:
- block没有捕获变量到block内,那block直接存储在
全局区
。 - 如果block捕获变量
- 此时block被
强引用
,则block存储在堆区,即堆区block
。 - 此时block被
弱引用(通过__weak修饰
),则block存储在栈区,即栈区block
。
- 此时block被
block底层分析
通过clang分析不同类型的block底层数据结构,怎么捕获外部变量。
数据结构
创建一个block
int a = 10;
void(^testBlock)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",testBlock);
通过 clang -rewrite-objc main.m -o mian.cpp 生成main.cpp文件,查看编译后的数据格式
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int a = 10;
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_12e473_mi_1,testBlock);
}
return 0;
}
去除类型转换的括号,简化为
void(*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
block的数据类型为__main_block_impl_0
,是一个结构体
,同时可以说明block
是一个__main_block_impl_0
类型的对象
,这也是为什么block能够%@打印的原因。
//block结构体类型
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
//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)};
//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_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_12e473_mi_0,a);
}
//block代码块的结构体类型
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
的本质是一个结构体
,其内部包含block基本的数据结构
、代码块的结构体
、方法访问的外部变量
。因为block数据结构内部有isa
指针,所以block本质也是对象。由于block函数没有名称,也被称为匿名函数
。
1、block为什么需要调用
block
底层的类型为__main_block_impl_0
结构体,通过其同名构造函数创建,第一个传入的block的内部实现代码块
,即__main_block_func_0
,用fp
表示,然后赋值给impl
的FuncPtr
属性,然后在main中进行了调用。如果不调用,block内部实现的代码块将无法执行,可以总结为以下两点
-
函数声明
:即block
内部实现声明成了一个函数__main_block_func_0
-
执行具体的函数实现
:通过调用block
的FuncPtr
指针,调用block
的代码块执行
2、block是如何获取外界变量的
block捕获外部值类型
捕获外部int是不能进行修改
int a = 10;
void(^testBlock)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"%@",testBlock);
编译后代码分析
int a = 10;
// 初始化block结构体 传入a
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
//去除类型转换简化后 void(*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
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;
}
};
__main_block_impl_0
初始化时,使用初始化列表来初始化字段设置结构体捕获的变量a值为10。
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy 值拷贝,即 a = 10,此时的a与传入的__cself的a并不是同一个
printf("CJL - %d", a);
}
执行block代码块,其内部重新初始变量a赋值。
总结:
block捕获外界变量时,在内部会自动生成同一个属性来保存,代码块内部会copy该属性执行。
block捕获外部对象
初始化一个数组,block捕获修改数组成功
编译后代码分析
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) {
NSMutableArray *array = __cself->array; // bound by copy
((void (*)(id, SEL, ObjectType _Nonnull, NSUInteger))(void *)objc_msgSend)((id)array, sel_registerName("setObject:atIndexedSubscript:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1), (NSUInteger)0);
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSMutableArray * array = ((NSMutableArray *(*)(id, SEL, ObjectType _Nonnull, ...))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("initWithObjects:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 8), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 9), ((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 10), __null);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_5ba1be_mi_0,array);
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344));
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_5ba1be_mi_1,array);
}
return 0;
}
-
__main_block_impl_0
初始化是传入数组,赋值给属性array
。 -
__main_block_func_0
代码块内操作捕获数组进行修改。
总结:
- 捕获的外界对象(强引用),操作原对象。
__block的原理
对变量a
进行__block
修饰,然后在block中对a进行++操作
__block int a = 10;
void(^testBlock)(void) = ^{
a++;
NSLog(@"%d",a);
};
testBlock();
NSLog(@"%@",testBlock);
通过底层编译如下
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__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
(a->__forwarding->a)++;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_3f1057_mi_0,(a->__forwarding->a));
}
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_sg_hb1bwpx16m9dprh1bryw63940000gp_T_main_3f1057_mi_1,testBlock);
}
return 0;
}
- 在main函数内部先初始化一个
__Block_byref_a_0
结构体,__Block_byref_a_0
初始时,将&a
强转类型为__Block_byref_a_0
结构体,再赋值到__forwarding
。 - 在
__main_block_impl_0
初始化时传入__Block_byref_a_0(&a)
,赋值给__Block_byref_a_0 *a
属性(强引用)
。 - 在
__main_block_func_0
代码块内,通过__cself->a
读取捕获的参数对象,再读取属性结构体内__forwarding(&a)
,取值进行处理。实则是操作捕获同一个内存空间
。
总结:
- 外界变量会生成
__Block_byref_a_0
结构体 - 结构体用来保存
原始变量的指针和值
- 将变量生成的结构体对象的指针地址传递给代码块,代码块内操作的就是原变量地址。