iOS ------ Block的相关问题
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,直接返回 NULLif (!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// 就只将引用计数加 1latching_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);// 开辟失败,返回 NULLif (!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 部分的位全部清为 0result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed// 将 result 标记位在堆上,需要手动释放;并且引用计数初始化为 1result->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 指向 _NSConcreteMallocBlockresult->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 copyNSLog((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 copyNSLog((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 copyNSLog((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 refNSLog((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))); // 1void(^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 copyNSLog((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))); // 1void(^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()指向的对象已经被释放了,形成野指针,所以程序崩溃了。
相关文章:

iOS ------ Block的相关问题
Block的定义 Block可以截获局部变量的匿名函数, 是将函数及其执行上下文封装起来的对象。 Block的实现 通过Clang将以下的OC代码转化为C代码 // Clang xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m//main.m #import <Foundation/Foundation.…...

conda issue
Conda 是一个跨平台、通用的二进制包管理器。它是 Anaconda 安装使用的包管理器,但它也可能用于其他系统。Conda 完全用 Python 编写,并且是 BSD 许可的开源。通用意味着大部分的包都可以用它进行管理,很像一个跨平台版本的apt或者yum&#x…...

为了解决地图引入鉴权失败的解决方案
在以下文件中需要添加相应代码 app/controller/CollageProduct.php app/view/designer_page/designer_editor.html app/view/designer_page/designer.html app/controller/Freight.php app\controller\Business.php app\controller\DesignerPage.php 只有这样才能保证htt…...

[ptrade交易实战] 第十八篇 期货查询类函数和期货设置类函数
前言 今天主要和大家分享的是期货查询类的函数和期货设置类的函数! 具体的开通渠道可以看文章末尾! 一、get_margin_rate—— 获取用户设置的保证金比例 保证金是期货交易中的一个重点,这个函数就是用来获取我们设置的保证金比例的&#…...

STM32智能家居控制系统教程
目录 引言环境准备智能家居控制系统基础代码实现:实现智能家居控制系统 4.1 数据采集模块 4.2 数据处理与分析模块 4.3 通信与网络系统实现 4.4 用户界面与数据可视化应用场景:家居监测与优化问题解决方案与优化收尾与总结 1. 引言 智能家居控制系统通…...

FPGA 中的 IOE与IO BANK
IO bank(输入/输出bank) 定义:IO bank 是 FPGA 中一组 IOE 的集合,通常共享相同的电源电压、时钟域和时序管理。每个 IO bank 包含多个 IOE,它们可以根据需要分配给不同的信号处理任务。作用:IO bank 的存…...

ADetailer模型+Stable Diffusion的inpainting功能是如何对遮罩区域进行修复生成的ADetailer
模型选则: face_yolov8n.pt 和 face_yolov8s.pt: 用途:用于人脸检测。特点:YOLOv8n 是轻量级版本,适合资源有限的设备;YOLOv8s 是标准版本,检测精度更高。 hand_yolov8n.pt: 用途&am…...

【博士每天一篇文献-综述】2024机器遗忘最新综述之一:An overview of machine unlearning
1 介绍 年份:2024 作者: 期刊: High-Confidence Computing(2区) 引用量:0 Li C, Jiang H, Chen J, et al. An overview of machine unlearning[J]. High-Confidence Computing, 2024: 100254 本文详细提供…...

【机器学习】Jupyter Notebook如何使用之基本步骤和进阶操作
引言 Jupyter Notebook 是一个交互式计算环境,它允许创建包含代码、文本和可视化内容的文档 文章目录 引言一、基本步骤1.1 启动 Jupyter Notebook1.2 使用 Jupyter Notebook 仪表板1.3 在笔记本中工作1.4 常用快捷键1.5 导出和分享笔记本 二、进阶用法2.1 组织笔…...

C++ | Leetcode C++题解之第279题完全平方数
题目: 题解: class Solution { public:// 判断是否为完全平方数bool isPerfectSquare(int x) {int y sqrt(x);return y * y x;}// 判断是否能表示为 4^k*(8m7)bool checkAnswer4(int x) {while (x % 4 0) {x / 4;}return x % 8 7;}int numSquares(i…...

Vue 3 响应式高阶用法之 `shallowRef()` 详解
Vue 3 响应式高阶用法之 shallowRef() 详解 文章目录 Vue 3 响应式高阶用法之 shallowRef() 详解简介一、使用场景1.1 深层嵌套对象的性能优化1.2 需要部分响应式的场景 二、基本使用2.1 引入 shallowRef2.2 定义 shallowRef 三、功能详解3.1 浅层响应式3.2 与 ref 的对比 四、…...

流量录制与回放:jvm-sandbox-repeater工具详解
在软件开发和测试过程中,流量录制与回放是一个非常重要的环节,它可以帮助开发者验证系统在特定条件下的行为是否符合预期。本文将详细介绍一款强大的流量录制回放工具——jvm-sandbox-repeater,以及如何利用它来提高软件测试的效率和质量。 …...

内网渗透—内网穿透工具NgrokFRPNPSSPP
前言 主要介绍一下常见的隧道搭建工具,以此来达到一个内网穿透的目的。简单说一下实验滴环境吧,kali作为攻击机,winserver2016作为目标靶机。 kali 192.168.145.171 winserver2016 10.236.44.127 显然它们处于两个不同的局域网,…...

嵌入式中传感器数据处理方法
大家好,在传感器使用中,我们常常需要对传感器数据进行各种整理,让应用获得更好的效果,以下介绍几种常用的简单处理方法: 加权平滑:平滑和均衡传感器数据,减小偶然数据突变的影响。 抽取突变:去除静态和缓慢变化的数据背景,强调瞬间变化。 简单移动平均线:保留数据流最…...

生成式 AI 的发展方向,是 Chat 还是 Agent?
据《福布斯》报道,商业的未来是自动化。他们报告说,自动化的应用是不可避免的,“工人们即将被一个圈子和一套规则包围,要严格遵守,不能偏离。得益于聊天机器人ChatGPT于2022年11月推出所带来的强劲加持,202…...

金字塔监督在人脸反欺骗中的应用
介绍 论文地址:https://arxiv.org/pdf/2011.12032.pdf 近年来,人脸识别技术越来越普及。在智能手机解锁和进出机场时,理所当然地会用到它。人脸识别也有望被用于管理今年奥运会的相关人员。但与此同时,人们对人脸欺骗的关注度也…...

vue3——两种利用自定义指令实现防止按钮重复点击的方法
方法一:利用定时器设置时间,下方代码设置时间为1秒 但是有个缺点:请求如果很慢,1秒钟还没有好,那么该方法就没用了 // 利用定时器:1秒之后才能再次点击app.directive(preventReClick, {mounted: (el, bind…...

Chrome谷歌浏览器Console(控制台)显示文件名及行数
有没有这样的困扰?Chrome谷歌浏览器console(控制台)不显示编译文件名及行数? 设置(Settings)- > 忽略列表(lgnore List)-> 自定义排除规则(Custom exclusion rules) 将自定义排除规则…...

Vue3+Element Plus 实现table表格中input的验证
实现效果 html部分 <template><div class"table"><el-form ref"tableFormRef" :model"form"><el-table :data"form.detailList"><el-table-column type"selection" width"55" align&…...

安宝特方案|解放双手,解决死角,AR带来质量监督新体验
AR质量监督 解放双手,解决死角 在当今制造业快速发展的背景下,质量监督成为确保产品高质量和完善的管理制度的关键环节。然而,传统的质量监督方式存在诸多挑战,如人工操作带来的效率低下、查岗不及时、摄像头死角等问题。 为了解…...

Django教程(005):基于ORM操作数据库的部门管理系统
文章目录 1、功能介绍2、新建项目3、创建app4、 表结构创建6、生成表7、静态文件管理8、部门管理8.1、部门列表8.2、添加部门8.3、删除部门8.4、编辑部门9、员工管理9.1、员工列表9.2、使用ModelForm添加员工9.3、编辑员工9.4、删除员工10、完整代码下载地址1、功能介绍 部门添…...

git等常用工具以及cmake
一、将git中的代码克隆进电脑以及常用工具介绍 1.安装git 首先需要安装git sudo apt install git 注意一定要加--recursive,因为文件中有很多“引用文件“,即第三方文件(库),加入该选项会将文件中包含的子模…...

Mybatis(四)特殊SQL的查询:模糊查询、批量删除、动态设置表明、添加功能获取自增的主键
实体类: 数据库: 1、模糊查询 方案一: 不适用#{ },’%?%‘ 问号是属于字符串的一部分 不会被解析成占位符,会被当作是我们字符串的一部分来解析,所以我们执行的语句中找不到占位符,但是我们却…...

JS原型与原型链
JS原型与原型链 JavaScript中一切引用类型都是对象,对象就是属性的集合。 Array类型、Function类型、Object类型、Date类型、RegExp类型等都是引用类型。 原型是什么 总计一句话就是(继承里的父亲,你可以使用你的原型里的函数)…...

Python编程学习第一篇——Python零基础快速入门(六)(4)异常处理
我们已经了解了Python的基本数据类型、变量和基本的逻辑控制语句,基于这些基础知识可以编写一些小程序了,但是在写程序的时候我们会发现,有时候程序并不是按我们预期的方向执行,有的直接报错,有的没有报错,…...

GraphHopper-map-navi_路径规划、导航(web前端页面版)
文章目录 一、项目地址二、踩坑环境三、问题记录3.1、graphhopper中地图问题3.1.1. getOpacity不存在的问题3.1.2. dispatchEvent不存在的问题3.1.3. vectorLayer.set(background-maplibre-layer, true)不存在set方法3.1.4. maplibre-gl.js.map不存在的问题3.1.5. Uncaught Ref…...

2-46 基于matlab的声音信号的短时能量、短时过零率、端点检测
基于matlab的声音信号的短时能量、短时过零率、端点检测。通过计算计算短时能量、调整能量门限,然后开始端点检测。输出可视化结果。程序已调通,可直接运行。 2-46 短时能量 短时过零率 端点检测 - 小红书 (xiaohongshu.com)...

力扣630.课程表 II
力扣630.课程表 II 反悔堆 将课程按照结束时间从大到小排序每次取一个判断当前是否能学完该课程如果能学完就将持续时间加入堆 更新答案如果学不完就判断该课程持续时间是否比之前学过的最大的还大 用时更短的话就将旧的弹出 class Solution {public:int scheduleCourse(ve…...

数字IC后端流程简述
1. 设计输入 目标:接收前端设计(如RTL代码和约束文件)的输出。 工具:前端设计工具(如Synopsys Design Compiler或Cadence Genus)。 步骤: 确保前端设计的RTL代码经过综合并生成了门级网表(Netlist)。 收集约束文件(Constraints),如时序约束(SDC文件)、功率约束等…...

数学建模--整数规划和非线性规划
目录 整数规划 非线性规划 总结 整数规划中分支定界法的具体步骤和实现细节是什么? 初始化: 分支: 定界: 剪枝: 终止条件: 非线性规划中的梯度法、牛顿法和拟牛顿法的比较分析有哪些?…...