一.block本质
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
对如下代码转换成c++代码进行分析
// auto:自动变量,离开作用域就销毁.如果不写默认就是auto auto int age = 10; static int height = 10; void (^block)(void) = ^{ // age的值捕获进来(capture) NSLog(@"age is %d, height is %d", age, height); }; age = 20; height = 20; block();
转成c++得出block的底层代码结构如图
截屏2022-03-24 下午3.05.14.png
block的底层结构如下图所示
image.png
二.block的变量捕获
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
image.png
三.block的类型
block有3种类型,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
NSLog(@"%@:%@:%@",[block class],[[block class] superclass],[[[block class] superclass] superclass]); 打印结果为__NSGlobalBlock__:NSBlock:NSObject
NSGlobalBlock ( _NSConcreteGlobalBlock )
NSStackBlock ( _NSConcreteStackBlock )
NSMallocBlock ( _NSConcreteMallocBlock )
image.png
block类型 | 环境 |
NSGlobalBlock | 没有访问auto变量 |
NSStackBlock | 访问了auto变量 |
NSMallocBlock | NSStackBlock 调用了copy |
1.没有访问auto void (^block)(void) = ^{ }; NSLog(@"%@",[block class]);//打印结果:__NSGlobalBlock__ 2.访问了auto auto int age = 10; void (^block)(void) = ^{ NSLog(@"%@",age); }; NSLog(@"%@",[block class]);//mrc环境打印结果:NSStackBlock 。ARC环境打印结果:__NSMallocBlock__,ARC环境下系统会自动调用copy到堆上。 3.NSStackBlock调用了copy void (^block)(void) = [^{ NSLog(@"%d",age); } copy];
每一种类型的block调用copy后的结果如下所示
block的类 | 副本源的配置储存域 | 复制效果 |
_NSConcreteStackBlock | 栈 | 从栈复制到堆 |
_NSConcreteGlobalBlock | 程序的数据区域 | 什么也不做 |
_NSConcreteMallocBlock | 堆 | 引用计数增加 |
四.block的copy
在ARC环境下,编译器会根据情况自动将栈上的block复制到推上,比如
1.block作为函数返回值时,ARC会自动进行copy操作;
typedef void(^MyBlock)(void); MyBlock returnBlock(){ //调用方法在栈上,调用完后被销毁 int age =10; void (^block)(void) = ^{ NSLog(@"%d",age); }; return ^{ NSLog(@"%d",age); }; } 当调用returnBlock()时相当于 block赋值给了MyBlock(产生强指针),这时候会对block进行copy void(^MyBlock)(void) = ^{ NSLog(@"%d",age); };
2.将block赋值给(强指针)__strong指针时,ARC会自动进行copy操作;
typedef void(^MyBlock)(void); void test(void){ //myblock强引用block MyBlock myblock = ^{ NSLog(@"%d",age); };//ARC会对block自动进行copy操作 }
3.block作为方法名中包含usingBlock的方法参数时
NSArray *array = @[]; [array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { }];
4.block作为GCD的方法参数时,ARC会自动进行copy操作;
static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ });
五.block的使用
MRC环境下block属性写法(使用copy)
@property (copy, nonatomic) void (^block)(void);
ARC环境下block写法(可以使用copy、strong)
@property (strong, nonatomic) void (^block)(void);
@property (copy, nonatomic) void (^block)(void);
六.当block访问auto
或者__block
修饰的对象类型
的变量时
block在栈上(没有copy操作):将不会对变量产生强引用
block在被拷贝到堆上(已有copy操作):
1
.会调用block内部的copy函数。
2
copy函数内部会调用_Block_object_assign函数。
3
_Block_object_assign函数会根据auto
变量的修饰符(__strong
强指针、__weak
弱指针、__unsafe_unretained
不是强引用)做出相应的操作,形成强引用或者弱引用(注意:ARC时auto和__block都会retain,MRC时auto会retain但__block不会retain)
block从堆上移除:
1
.会调用block内部dispose函数。
2
.dispose函数内部会调用_Block_object_dispose函数。
3
._Block_object_dispose函数会自动释放引用的auto
变量(release)
如下源码分析
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) { _Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/); } static void __main_block_dispose_0(struct __main_block_impl_0*src) { _Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/); } static struct __main_block_desc_0 { size_t reserved; size_t Block_size; //如果访问的是对象类型就会自动生成这两个函数,对他内部需要访问的对象类型变量进行内存管理操作return或 release //block在被拷贝到堆上时会调用copy函数,对变量形成强引用或弱引用 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); //block从堆上移除时会调用block内部dispose函数释放变量release 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};
函数 | 调用时机 |
copy函数 | 栈上的block复制到堆时 |
dispose函数 | 堆上的block被废弃时 |
七.__block修饰符
block内部无法修改auto变量值
image.png
1.使用__block修饰后可以修改auto变量的值
image.png
2.__block不能修饰全局变量,静态变量
image.png
image.png
3.编译器会将__block修饰过的变量包装成对象
__block int age = 10; void (^block)(void) = ^{ age = 23; }; age = 123; 转成c++代码 __Block_byref_age_0 age = {0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10}; void (*block)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344)); (age.__forwarding->age) = 123; struct __main_block_impl_0 { struct __block_impl impl; struct __main_block_desc_0* Desc; __Block_byref_age_0 *age; // 包装后的age对象 }; struct __Block_byref_age_0 { void *__isa;//对象isa __Block_byref_age_0 *__forwarding;//指向自己的指针 int __flags;//标识 int __size;//结构体所占内存大小 int age;//age变量 }; static void __main_block_func_0(struct __main_block_impl_0 *__cself) { __Block_byref_age_0 *age = __cself->age; //在结构体__main_block_impl_0中找到__Block_byref_age_0 *age对象; (age->__forwarding->age) = 23; }
__forwarding为什么指向自身的指针
image.png
从下图可以看出如果在栈区__forwarding会指向栈区结构体地址如果复制到堆后__forwarding会指向堆区结构体地址
image.png
八.__block的内存管理
当block在在栈上时,并不会对__block变量产生强引用,没有调用copy函数还在栈上,栈上的变量随时会销毁
当block被copy到堆时,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会对__block变量形成强引用
image.png
当block从堆中移除时
会调用block内部的dispose函数->dispose函数内部会调用__Block_object_dispose函数-> __Block_object_dispose函数会自动释放引用的__block变量(release)
image.png
九.auto
变量、__block
修饰对象类型的变量时
1.当block在栈上时,对他们都不会产生强引用
2.当block拷贝到堆上时,都会通过copy函数来处理它们
_Block_object_assign((void*)&dst->per1, (void*)src->per1, 3/*BLOCK_FIELD_IS_OBJECT*/);auto对象类型变量 (flag = 8 BLOCK_FIELD_IS_OBJECT ),当copy到堆上时,block会调用了[obj retain]方法,引用计数器会+1, 否则复制到堆上时,会对找不到该变量(因为局部变量受作用域影响,肯定比block的生命周期短),且在复制的过程中,通过fowarding指针可以找到堆上的该变量。 _Block_object_assign((void*)&dst->per2, (void*)src->per2, 8/*BLOCK_FIELD_IS_BYREF*/);加了__block对象类型变量 flag = 3 (BLOCK_FIELD_IS_BYREF),即使复制多次也只会`复制一次`,后面只是将该变量的`引用计数器+1`(但其实对于基本数据类型的话,我们也不会去关心引用计数器)。不加__block的话,相当于block结构体对基本数据类型对象做了一次const化到了结构体内,只能使用,不能修改和赋值。