Block的定义
Block可以截获局部变量的匿名函数, 是将函数及其执行上下文封装起来的对象。
Block的实现
通过Clang将以下的OC代码转化为C++代码
// Clang
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
//main.m
#import <Foundation/Foundation.h>
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"我是 block");
};
block();
}
return 0;
}
//参数结构体
struct __main_block_impl_0 {
struct __block_impl impl;// block 结构体
struct __main_block_desc_0* Desc;// block 的描述对象
/*
block 的构造函数
** 返回值:__main_block_impl_0 结构体
** 参数一:__main_block_func_0 结构体
** 参数二:__main_block_desc_0 结构体的地址
** 参数三:flags 标识位
*/
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;// &_NSConcreteStackBlock 表示存储在栈上
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//block 结构体
struct __block_impl {
void *isa;//block 的类型
int Flags;
int Reserved;
void *FuncPtr;// block的执行函数指针,指向__main_block_func_0
};
//封装了 block 中的代码
//参数是__main_block_impl_0结构体的指针
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_03dcda_mi_0);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;// block 所占的内存空间
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
/*
** void(^block)(void) = ^{
NSLog(@"调用了block");
};
** 定义block的本质:
** 调用__main_block_impl_0()构造函数
** 并且给它传了两个参数 __main_block_func_0 和 &__main_block_desc_0_DATA
** __main_block_func_0 封装了block里的代码
** 拿到函数的返回值,再取返回值的地址 &__main_block_impl_0,
** 把这个地址赋值给 block
*/
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
/*
** block();
** 调用block的本质:
** 通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数地址,直接调用
*/
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
定义block的本质:调用__main_block_impl_0()构造函数并且给它传了两个参数 __main_block_func_0
和 &__main_block_desc_0_DATA
,__main_block_func_0 封装了block里的代码,拿到函数的返回值,再取返回值的地址 &__main_block_impl_0, 把这个地址赋值给 block
__main_block_impl_0
struct __main_block_impl_0 {
struct __block_impl impl;// block 结构体
struct __main_block_desc_0* Desc;// block 的描述对象
/*
block 的构造函数
** 返回值:__main_block_impl_0 结构体
** 参数一:__main_block_func_0 结构体
** 参数二:__main_block_desc_0 结构体的地址
** 参数三:flags 标识位
*/
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;// &_NSConcreteStackBlock 表示存储在栈上
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__block_impl
//block 结构体
struct __block_impl {
void *isa;//block 的类型
int Flags;
int Reserved;
void *FuncPtr;// block的执行函数指针,指向__main_block_func_0
};
调用block的本质:
通过 __main_block_impl_0 中的 __block_impl 中的 FuncPtr 拿到函数__main_block_func_0的地址,直接调用。
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
__main_block_func_0
封装了 block 中的代码,参数是__main_block_impl_0结构体的指针
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_03dcda_mi_0);
}
总结:
先定义block
,通过__main_block_impl_0
构造函数初始化__block_impl的block结构体,其中包括执行block执行函数 __main_block_func_0的地址FuncPtr
。 再调用Block,转换 Block 类型为 __block_impl
,通过 FuncPtr
获取执行函数地址,调用执行函数 __main_block_func_0
。
Block的种类
block的分类主要分为以下的三种:
NSGlobalBlock 全局block
NSStackBlock 栈区block
NSMallocBlock 堆区block
int b = 20;
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
static int a = 10;
int c = 30;
NSObject* obj = [[NSObject alloc] init];
void (^Block0)(void) = ^{
NSLog(@"Block");
};
NSLog(@"Block0 %@",Block0);
void (^Block1)(void) = ^{
NSLog(@"a = %d b = %d",a, b);
};
NSLog(@"Block1 %@",Block1);
void (^Block2)(void) = ^{
NSLog(@"c = %d",c);
};
NSLog(@"Block2 %@",Block2);
void (^Block3)(void) = ^{
NSLog(@"%@",obj);
};
NSLog(@"Block3 %@",Block3);
void (^__weak Block4)(void) = ^{
NSLog(@"c = %d",c);
};
NSLog(@"Block4 %@",Block4);
void(^block)(void);
block = [Block4 copy];
NSLog(@"block %@",block);
}
运行结果:
2024-07-23 21:45:38.640468+0800 Block总结[91605:3226729] Block0 <__NSGlobalBlock__: 0x104ae40d0>
2024-07-23 21:45:38.640558+0800 Block总结[91605:3226729] Block1 <__NSGlobalBlock__: 0x104ae40f0>
2024-07-23 21:45:38.640606+0800 Block总结[91605:3226729] Block2 <__NSMallocBlock__: 0x60000355a670>
2024-07-23 21:45:38.640659+0800 Block总结[91605:3226729] Block3 <__NSMallocBlock__: 0x60000357c690>
2024-07-23 21:45:38.640705+0800 Block总结[91605:3226729] Block4 <__NSStackBlock__: 0x16b31b930>
2024-07-23 21:45:38.640750+0800 Block总结[91605:3226729] block <__NSMallocBlock__: 0x60000357c8a0>
从上面可以总结出:
GlobalBlock:
- 位于全局变量
- 在Block的内部不使用外部变量,或者只使用静态变量或全局变量
NSMallocBlock:
- 在Block内部使用局部变量或者OC的属,并且赋值给强引用或者Copy修饰的变量
NSStackBlock
- 位于栈区
- 与mallocBlock一样,可以在内部使用局部变量或者OC属性
- 但是不能赋值给强引用或者Copy修饰的变量
三种类型的Block的底层
- 全局block在运行时调用_Block_copy方法后,仍然是全局block
- 堆区block是由编译时的栈区block在运行时调用_Block_copy方法,生成新的堆区block
- 栈区blcok不会进行_Block_copy的操作
- 结论:如果block赋值给强引用或者copy修饰的变量,那么block会进行(objc_retainBlock中的_Block_copy操作,如果是赋值给__weak修饰的变量则不会进行_Block_copy的操作
_Block_copy方法
// Copy, or bump refcount, of a block. If really copying, call the copy helper if present.
// 拷贝 block,
// 如果原来就在堆上,就将引用计数加 1;
// 如果原来在栈上,会拷贝到堆上,引用计数初始化为 1,并且会调用 copy helper 方法(如果存在的话);
// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
// 参数 arg 就是 Block_layout 对象,
// 返回值是拷贝后的 block 的地址
// 运行?stack -》malloc
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
// 如果 arg 为 NULL,直接返回 NULL
if (!arg) return NULL;
// The following would be better done as a switch statement
// 强转为 Block_layout 类型
aBlock = (struct Block_layout *)arg;
const char *signature = _Block_descriptor_3(aBlock)->signature;
// 如果现在已经在堆上
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
// 就只将引用计数加 1
latching_incr_int(&aBlock->flags);
return aBlock;
}
// 如果 block 在全局区,不用加引用计数,也不用拷贝,直接返回 block 本身
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
else {
// Its a stack block. Make a copy.
// block 现在在栈上,现在需要将其拷贝到堆上
// 在堆上重新开辟一块和 aBlock 相同大小的内存
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
// 开辟失败,返回 NULL
if (!result) return NULL;
// 将 aBlock 内存上的数据全部复制新开辟的 result 上
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
result->invoke = aBlock->invoke;
#endif
// reset refcount
// 将 flags 中的 BLOCK_REFCOUNT_MASK 和 BLOCK_DEALLOCATING 部分的位全部清为 0
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
// 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
// copy 方法中会调用做拷贝成员变量的工作
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
// isa 指向 _NSConcreteMallocBlock
result->isa = _NSConcreteMallocBlock;
return result;
}
}
在 ARC 环境下,编译器会根据情况,自动将 Block 从栈上 copy 到堆上。具体会进行 copy 的情况有如下 4 种
- block作为函数的返回值
- block赋值给__strong指针,或者赋值给block类型的成员变量时
- block作为Cocoa API方法中国呢含有using Block的方法参数
- block作为GCDAPI的方法参数时
__block变量
在使用__block变量的Block从栈复制到堆上时,__block变量也会受到影响
按照oc的内存管理机制来管理,此时两者的关系就从block使用__block变成了block持有__block(__Block_byref_age_0结构体对象)
讲过的使用__block变量用结构体成员变量__forwarding
的原因。“不管__block变量配置在栈上还是堆上,都能够正确访问该变量”
__block int val = 0;
void (^blk) (void) = [^{++val;
NSLog(@"%d", val);
NSLog(@"%p", &val);
} copy];
++val;
blk();
NSLog(@"%d", val);
NSLog(@"%p", &val);
输出结果:
2024-07-25 15:26:02.624764+0800 block捕获OC对象[13116:500565] 2
2024-07-25 15:26:02.625028+0800 block捕获OC对象[13116:500565] 0x600000203398
2024-07-25 15:26:02.625045+0800 block捕获OC对象[13116:500565] 2
2024-07-25 15:26:02.625055+0800 block捕获OC对象[13116:500565] 0x600000203398
在Block语法的函数中,该变量val为复制到堆上的_block变量,而使用Block语法外的与Block无关的变量val,为复制前栈上的__block变量用结构体实例。
但是栈上的__block变量用结构体实例在__block变量从栈复制到堆上时,会将成员变量__forwarding
的值替换为复制目标堆上的__block变量用结构体实例的地址。
无论在Block语法中,Block语法外使用__block变量,还是__block变量配置在堆上或栈上,都可以顺利访问堆上同一个__block
变量。
Block循环引用
循环引用的案例
typedef void(^TBlock)(void);
@interface ViewController ()
@property (nonatomic, strong) TBlock block;
@property (nonatomic, copy) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 循环引用
self.name = @"Hello";
self.block = ^(){
NSLog(@"%@", self.name);
};
self.block();
}
这里self持有block,block也持有了self。两者的释放都要等待对方先释放。就会造成循环引用。造成内存泄露。
解决方法
- weak的使用
// 循环引用
self.name = @"Hello";
__weak typeof(self) weakSelf = self;
self.block = ^(){
NSLog(@"%@", weakSelf.name);
};
self.block();
此时self持有block,block弱引用self,强持有中断,所以不会引起循环引用。
- 强弱共舞
self.name = @"Hello";
__weak typeof(self) weakSelf = self;
self.block = ^(){
__strong __typeof(weakSelf)strongWeak = weakSelf;
NSLog(@"%@", strongWeak.name);
};
self.block();
__strong
的作用是防止block内部引用的外部weak变量被提前释放,进而在Block内部无法获取weak变量进而继续使用的情况。这样就保证了在block作用域结束之前,block内部都持有一个strongSelf对象可供使用。
- 手动中断持有关系
self.name = @"Hello";
__block ViewController * ctrl = self;
self.block = ^(){
NSLog(@"%@", ctrl.name);
ctrl = nil;
};
self.block();
ctrl在block使用完成后,被指为nil,block对self持有关系就解除了,不构成循环引用。
- block传参
// 循环引用
self.name = @"Hello";
self.block = ^(ViewController * ctrl){
NSLog(@"%@", ctrl.name);
};
self.block(self);
将self
作为参数传入block
,进行指针拷贝,并没有对self进行持有
Block变量截获
- 对于全局变量,不会捕获到block内部,访问方为直接访问
- 对于auto类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,访问方式为值传递
- 对于static类型的局部变量,会捕获到block内部,block内部会自动生成一个成员变量,访问方式为指针传递
- 对于对象类型的局部变量,block会连同修饰符一起捕获
auto自动变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
block();
}
return 0;
}
将以下 OC 代码转换为 C++ 代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;//生成的变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_40c716_mi_0,age);
}
- 在__main_block_impl_0结构体中会自动生成一个相同类型的age变量
- 构造函数__main_block_impl_0中多出了一个age参数,用来捕获外部的变量
由于传递方式为值传递,所以我们在block外部修饰age变量时,不会影响到block中的age变量
static类型的局部变量
static int age = 10;
void(^block)(void) = ^{
NSLog(@"%d",age);
};
age = 20;
block();
// 20
将以下OC代码转化为C++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int *age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_cb7943_mi_0,(*age));
}
- __main_block_impl_0结构体中生成了一个相同类型的age变量指针
- __main_block_impl_0构造函数多了个参数,用来捕获外部的age变量的地址
由于传递方式是指针传递,所以修改局部变量age时,age的值会随之变化
全局变量
int height = 10;
static int age = 20;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void(^block)(void) = ^{
NSLog(@"%d,%d",height,age);
};
block();
}
return 0;
}
将以下OC代码转化为C++代码
int height = 10;
static int age = 20;
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) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_7a340f_mi_0,height,age);
}
__main_block_impl_0结构体中,并没有自动生成age和height全局变量,也就是说没有将变量捕获到block内部
为什么局部变量需要捕获,而全局变量不用呢?
- 作用域的原因,全局变量哪里都可以直接访问,所以不用捕获
- 局部变量,外部不能直接访问,所以需要捕获
- auto类型的局部变量可能会销毁,其内存会消失,block将来执行代码的时候不可能再去访问呢块内存,所以捕获其值
- static变量会一直保存在内存中,所以捕获其地址即可
OC对象
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSObject* obj = [[NSObject alloc] init];
void(^block)(void) = ^{
NSLog(@"%@", obj);
};
block();
}
return 0;
}
将以下OC代码转化为C++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *obj;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSObject *obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_46_qzlmlhgd0xd2xjp590bfb5_00000gn_T_main_81a5a1_mi_0, obj);
}
- __main_block_impl_0结构体中生成了一个NSObject对象指针
- __main_block_impl_0构造函数多了个参数,用来捕获外部的NSObject对象的地址(值拷贝)
_block修饰的变量
- _block同于解决block内部无法修改auto变量值的问题;
- _block不能修饰全局变量,静态变量;
- 编译器会将_block变量包装成一个对象(struct __Block_byref_age_0(byref:按地址传递));
- 加_block修饰不会改变变量的性质,他还是auto变量;
- 一般情况,对捕获变量进行赋值(赋值!=使用)操作需要添加_block修饰符,比如给数组添加或删除对象,就不用加_bolck修饰符;
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
void(^block)(void) = ^{
age = 20;
NSLog(@"block-%d",age);
};
block();
NSLog(@"%d",age);
}
return 0;
}
block修饰局部变量
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
void(^block)(void) = ^{
age = 20;
NSLog(@"block-%d",age);
};
block();
NSLog(@"%d",age);
}
return 0;
}
将以下OC代码转化为C++代码
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;//持有指向该实例自身的指针
int __flags;
int __size;
int age;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_age_0 *_age, int flags=0) : age(_age->__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_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yx_7jg_wdg128v45l4cn_1g265h0000gn_T_main_75529b_mi_0,(age->__forwarding->age));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
- 编译器会将_block修饰的变量包装成一个__Block_byref_age_0结构体对象
- 以上age = 20 的赋值过程:通过block的
__main_block_imp_0
结构体实例中( __Block_byref_age_0)类型的age指针,找到__Block_byref_age_0
结构体的对象,__Block_byref_age_0
结构体对象持有指向实例本身的__forwarding
指针,通过成员变量_forwarding访问 __Block_byref_age_0结构体里的age变量,并将值改为20;
__block修饰对象
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block NSObject* obj = [[NSObject alloc] init];
NSLog(@"%@", obj);
void(^block)(void) = ^{
NSLog(@"%@", obj);
obj = [[NSObject alloc] init];
NSLog(@"%@", obj);
};
block();
}
return 0;
}
将以下OC代码转化为C++代码
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_obj_0 *obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__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_obj_0 *obj = __cself->obj; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_46_qzlmlhgd0xd2xjp590bfb5_00000gn_T_main_155262_mi_1, (obj->__forwarding->obj));
(obj->__forwarding->obj) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_46_qzlmlhgd0xd2xjp590bfb5_00000gn_T_main_155262_mi_2, (obj->__forwarding->obj));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 8/*BLOCK_FIELD_IS_BYREF*/);}
void (__Block_byref_id_object_copy)(void, void*);
这是一个函数指针,指向一个用于复制 Block 中引用的对象的函数。当 Block 被复制到堆上(例如,通过 Block_copy)时,这个函数将被调用以确保被捕获的对象也被正确地复制。
调用后Block就持有了该对象,在栈上的时候只是使用并没有持有注意这里的对象是__Block_byref_age_0结构体对象,对于obj对象并没有持有
void (__Block_byref_id_object_dispose)(void);
这是另一个函数指针,指向一个用于释放或清理 Block 中引用的对象的函数。当 Block 被释放(例如,通过 Block_release)时,这个函数将被调用以确保被捕获的对象也被正确地释放。
总结如图
Block的内存管理
对外部变量的引用计数
- 自动局部基本类型变量,因为是值传递,内存是跟随Block,不用干预
- static局部基本类型变量,指针传递,由于分配在静态区,故不用干预
- 全局变量,存储在数据去,不用干预
- 局部对象变量,如果在栈上,只是使用,不用干预。但Block在拷贝到堆时,对其retain,在Block对象销毁时,对其release
示例一
NSObject *objc = [NSObject new];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
void(^strongBlock)(void) = ^{
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
void(^__weak weakBlock)(void) = ^{
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
输出结果:
2024-07-24 09:46:10.696422+0800 Block总结[5902:3676907] 1
2024-07-24 09:46:10.696469+0800 Block总结[5902:3676907] ---3
2024-07-24 09:46:10.696501+0800 Block总结[5902:3676907] ---4
2024-07-24 09:46:10.696542+0800 Block总结[5902:3676907] ---5
为什么第二个打印3
我们可以看一看这段源码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *obj;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSObject *_obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSObject *obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_46_qzlmlhgd0xd2xjp590bfb5_00000gn_T_main_81a5a1_mi_0, obj);
}
编译时这段直接捕获局部OC对象,将obj指针拷贝过来并且赋值,引用计数加一。运行时block还会从栈区拷贝到堆区,obj的引用计数还会加一
示例二
__block NSObject *objc = [[NSObject alloc] init];
NSLog(@"%@",objc);
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
void(^strongBlock)(void) = ^{
NSLog(@"%@",objc);
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
NSObject* objc1 = objc;
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
输出结果
2024-07-24 20:59:00.137761+0800 block捕获OC对象[8663:256819] <NSObject: 0x600000008050>
2024-07-24 20:59:00.137770+0800 block捕获OC对象[8663:256819] 1
2024-07-24 20:59:00.137780+0800 block捕获OC对象[8663:256819] <NSObject: 0x600000008050>
2024-07-24 20:59:00.137788+0800 block捕获OC对象[8663:256819] ---1
2024-07-24 20:59:00.137795+0800 block捕获OC对象[8663:256819] ---2
2024-07-24 20:59:00.137802+0800 block捕获OC对象[8663:256819] ---1
为什么第二个输出1?
先看一下源码
struct __Block_byref_obj_0 {
void *__isa;
__Block_byref_obj_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSObject *obj;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_obj_0 *obj; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_obj_0 *_obj, int flags=0) : obj(_obj->__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_obj_0 *obj = __cself->obj; // bound by ref
}
编译器会将_block修饰的变量包装成一个__Block_byref_age_0结构体对象
__Block_byref_obj_0 *obj = __cself->obj;
当块捕获 __block 变量时,它实际上捕获的是 __Block_byref 结构体的指针,而不是变量本身。这意味着块捕获的是指向 __block 变量的间接引用,而不是直接引用
__block 修饰符时,编译器会对该变量进行特殊处理:
__block 变量在栈上分配内存。
当块(block)被复制到堆上时,__block 变量的内存也会被复制到堆上。
Block堆栈释放差异
#pragma mark - block 堆栈释放差异
- (void)jpreno {
int reno = 10;
void(^__weak weakBlock)(void) = nil;
{
// 栈区
void(^__weak strongBlock)(void) = ^{
NSLog(@"jp1:---%d", reno);
};
weakBlock = strongBlock;
NSLog(@"jp2:--%@--%@",weakBlock,strongBlock);
}
weakBlock();
}
输出结果
2024-07-24 11:08:23.993541+0800 Block总结[7484:3735630] jp2:--<__NSStackBlock__: 0x16b9939b0>--<__NSStackBlock__: 0x16b9939b0>
2024-07-24 11:08:23.993625+0800 Block总结[7484:3735630] jp1:---10
通过赋值weakBlock = strongBlock操作,两个block同时指向一个NSStackBlock,代码块运行结束后不会被释放,能够正常运行 weakBlock();
int reno = 10;
void(^__weak weakBlock)(void) = nil;
{
// 栈区
void(^__strong strongBlock)(void) = ^{
NSLog(@"jp1:---%d", reno);
};
weakBlock = strongBlock;
NSLog(@"jp2:--%@--%@",weakBlock,strongBlock);
}
weakBlock();
由于weakBlock不能强引用NSMallocBlock,代码块运行结束就会被释放。此时调用weakBlock()指向的对象已经被释放了,形成野指针,所以程序崩溃了。