1、如下代码,会出现什么问题?

  1. @interface Person : NSObject
  2. @property(nonatomic, copy) NSString *name;
  3. @property(nonatomic, copy) void(^block)(void);
  4. @end
  5. @implementation Person
  6. - (void)test {
  7. self.block = ^{
  8. NSLog(@"%@",_name);
  9. };
  10. }
  11. @end

解答:会出现循环引用问题,并且xcode会触发2条编译警告⚠️

  • Block implicitly retains 'self'; explicitly mention 'self' to indicate this is intended behavior
  • Capturing 'self' strongly in this block is likely to lead to a retain cycle

对成员变量在block中直接访问,造成block->Person的强引用,Person->block的强引用,最终造成循环引用。

修改代码:

  1. - (void)test {
  2. self.block = ^{
  3. NSLog(@"%@",self->_name);
  4. };
  5. }

这种情况下,会造成循环引用,Xcode编译器会发出⚠️

  • Capturing 'self' strongly in this block is likely to lead to a retain cycle

继续修改代码

  1. - (void)test {
  2. Person *p = self;
  3. self.block = ^{
  4. NSLog(@"%@",p->_name);
  5. //NSLog(@"%@",p.name);
  6. };
  7. }

这种情况下,会造成循环引用问题,编译器不会警告

继续修改代码

  1. - (void)test {
  2. __block Person *p = self;
  3. self.block = ^{
  4. NSLog(@"%@",p.name);
  5. };
  6. }

这种情况下,依然会造成循环引用问题

继续修改代码

  1. - (void)test {
  2. Person *p = [Person new];
  3. p.block = ^{
  4. NSLog(@"%@",_name);
  5. };
  6. }

这种情况下,不会造成循环引用问题,但Xcode会发出编译器警告⚠️

  • Block implicitly retains 'self'; explicitly mention 'self' to indicate this is intended behavior

继续修改代码

  1. - (void)test {
  2. __weak Person *p = self;
  3. self.block = ^{
  4. NSLog(@"%@",p.name);
  5. };
  6. }

这种情况下,不会造成循环引用。dealloc正常被调用

继续修改代码

  1. - (void)test {
  2. __unsafe_unretained Person *p = self;
  3. self.block = ^{
  4. NSLog(@"%@",p.name);
  5. };
  6. }

这种情况下,不会造成循环引用。

问题1总结:

  • block里面使用self并不一定都会造成循环引用
  • self强引用了block,则block中使用self或者成员变量都会造成循环引用
  • block不能解决循环引用,weak和__unsafe_unretained能解决循环引用问题

2、weak和strong,为什么需要使用__strong

  1. __weak __typeof(self)weakSelf = self; //1
  2. [self.context performBlock:^{
  3. [weakSelf doSomething]; //2
  4. __strong __typeof(weakSelf)strongSelf = weakSelf; //3
  5. [strongSelf doSomething];
  6. }]
  1. 使用weak typeof是在编译的时候,另外创建一个weak对象来操作self。
  2. 因为weakSelf和self是两个内容,doSomething有可能就直接对self自身引用计数减到0了. 所以在[weakSelf doSomething]的时候,你很难控制这里self是否就会被释放了.weakSelf只能看着。
  3. strong typeof在编译的时候,实际是对weakSelf的强引用。指针连带关系self的引用计数还会增加.但是你这个是在block里面,生命周期也只在当前block的作用域。所以,当这个block结束, strongSelf随之也就被释放了.同时也不会影响block外部的self的生命周期。

3、Block对变量的捕获

如下代码,打印的结果是多少?

  1. - (void)test2 {
  2. NSInteger a = 3; // 局部变量
  3. NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
  4. return n * a;
  5. };
  6. a = 1;
  7. printf("%zd \n",block(2));
  8. }

打印结果:
image.png
打印的结果是6,Block对局部变量的截获并没有因为a重新赋值而发生变化。

继续修改代码

  1. - (void)test2 {
  2. static NSInteger a = 3; //局部静态变量
  3. NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
  4. return n * a;
  5. };
  6. a = 1;
  7. printf("%zd \n",block(2));
  8. }

打印结果
image.png
打印的结果是2,a发生变化之后block内部的a也发生了改变。

继续修改代码

  1. NSInteger a = 3; //全局变量
  2. - (void)test2 {
  3. NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
  4. return n * a;
  5. };
  6. a = 1;
  7. printf("%zd \n",block(2));
  8. }

打印结果
image.png
从打印结果发现,全局变量a修改之后影响到了Block内部的a发生变化

继续修改代码

  1. static NSInteger a = 3;
  2. - (void)test2 {
  3. NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
  4. return n * a;
  5. };
  6. a = 1;
  7. printf("%zd \n",block(2));
  8. }

打印结果
image.png
打印输出是2,全局静态变量a在修改之后也直接影响到了block中的值

继续修改代码

  1. - (void)test2 {
  2. __block NSInteger a = 3;
  3. NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
  4. return n * a;
  5. };
  6. a = 1;
  7. printf("%zd \n",block(2));
  8. }

打印结果
image.png
打印输出是2,说明__block修饰的局部变量在Block内部是指针捕获

继续修改代码

  1. __block NSInteger a = 3;
  2. - (void)test2 {
  3. NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
  4. return n * a;
  5. };
  6. a = 1;
  7. printf("%zd \n",block(2));
  8. }

编译报错,提示❌

  • __block attribute not allowed, only allowed on local variables

**

使用clang查看局部变量,全局变量,局部静态变量,全局静态变量,__block变量的底层代码结构
源代码:

  1. NSInteger n1 = 3;
  2. static NSInteger n2 = 3;
  3. - (void)test3 {
  4. NSInteger n3 = 3;
  5. static NSInteger n4 = 3;
  6. __block NSInteger n5 = 3;
  7. void(^block)(void) = ^void(){
  8. NSLog(@"%zd",n1);
  9. NSLog(@"%zd",n2);
  10. NSLog(@"%zd",n3);
  11. NSLog(@"%zd",n4);
  12. NSLog(@"%zd",n5);
  13. };
  14. block();
  15. }

clang编译后的C++代码

  1. NSInteger n1 = 3;
  2. static NSInteger n2 = 3;
  3. struct __Block_byref_n5_1 {
  4. void *__isa;
  5. __Block_byref_n5_1 *__forwarding;
  6. int __flags;
  7. int __size;
  8. NSInteger n5;
  9. };
  10. struct __Person__test3_block_impl_0 {
  11. struct __block_impl impl;
  12. struct __Person__test3_block_desc_0* Desc;
  13. NSInteger n3;
  14. NSInteger *n4;
  15. __Block_byref_n5_1 *n5; // by ref
  16. __Person__test3_block_impl_0(void *fp, struct __Person__test3_block_desc_0 *desc, NSInteger _n3, NSInteger *_n4, __Block_byref_n5_1 *_n5, int flags=0) : n3(_n3), n4(_n4), n5(_n5->__forwarding) {
  17. impl.isa = &_NSConcreteStackBlock;
  18. impl.Flags = flags;
  19. impl.FuncPtr = fp;
  20. Desc = desc;
  21. }
  22. };
  23. static void __Person__test3_block_func_0(struct __Person__test3_block_impl_0 *__cself) {
  24. __Block_byref_n5_1 *n5 = __cself->n5; // bound by ref
  25. NSInteger n3 = __cself->n3; // bound by copy
  26. NSInteger *n4 = __cself->n4; // bound by copy
  27. NSLog((NSString *)&__NSConstantStringImpl__var_folders_5m_900ng1910q18rfy4jrhcbdm40000gn_T_Person_ef54cb_mi_1,n1);
  28. NSLog((NSString *)&__NSConstantStringImpl__var_folders_5m_900ng1910q18rfy4jrhcbdm40000gn_T_Person_ef54cb_mi_2,n2);
  29. NSLog((NSString *)&__NSConstantStringImpl__var_folders_5m_900ng1910q18rfy4jrhcbdm40000gn_T_Person_ef54cb_mi_3,n3);
  30. NSLog((NSString *)&__NSConstantStringImpl__var_folders_5m_900ng1910q18rfy4jrhcbdm40000gn_T_Person_ef54cb_mi_4,(*n4));
  31. NSLog((NSString *)&__NSConstantStringImpl__var_folders_5m_900ng1910q18rfy4jrhcbdm40000gn_T_Person_ef54cb_mi_5,(n5->__forwarding->n5));
  32. }
  33. static void __Person__test3_block_copy_0(struct __Person__test3_block_impl_0*dst, struct __Person__test3_block_impl_0*src) {_Block_object_assign((void*)&dst->n5, (void*)src->n5, 8/*BLOCK_FIELD_IS_BYREF*/);}
  34. static void __Person__test3_block_dispose_0(struct __Person__test3_block_impl_0*src) {_Block_object_dispose((void*)src->n5, 8/*BLOCK_FIELD_IS_BYREF*/);}
  35. static struct __Person__test3_block_desc_0 {
  36. size_t reserved;
  37. size_t Block_size;
  38. void (*copy)(struct __Person__test3_block_impl_0*, struct __Person__test3_block_impl_0*);
  39. void (*dispose)(struct __Person__test3_block_impl_0*);
  40. } __Person__test3_block_desc_0_DATA = { 0, sizeof(struct __Person__test3_block_impl_0), __Person__test3_block_copy_0, __Person__test3_block_dispose_0};
  41. static void _I_Person_test3(Person * self, SEL _cmd) {
  42. NSInteger n3 = 3;
  43. static NSInteger n4 = 3;
  44. __attribute__((__blocks__(byref))) __Block_byref_n5_1 n5 = {(void*)0,(__Block_byref_n5_1 *)&n5, 0, sizeof(__Block_byref_n5_1), 3};
  45. void(*block)(void) = ((void (*)())&__Person__test3_block_impl_0((void *)__Person__test3_block_func_0, &__Person__test3_block_desc_0_DATA, n3, &n4, (__Block_byref_n5_1 *)&n5, 570425344));
  46. ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
  47. }

分析以上C++代码,可以知道,Block对全局变量,静态全局变量是直接使用,在Block内部没有做任何操作,所以Block没有对全局变量和静态全局变量进行捕获。
__Person__test3_block_impl_0内部可以看出block转化为结构体对象之后,会自动生成对应的属性 NSInteger n3;``NSInteger *n4;``__Block_byref_n5_1 *n5;block修饰的局部变量也会生成一个结构体Block_byref_n5_1。可以看出NSInteger *n4;__Block_byref_n5_1 *n5;是指针类型。
再继续分析Persontest3_block_func_0可以看出,这里进行了一次赋值操作

  1. __Block_byref_n5_1 *n5 = __cself->n5; // bound by ref
  2. NSInteger n3 = __cself->n3; // bound by copy
  3. NSInteger *n4 = __cself->n4; // bound by copy

可以看出是对生成的结构体属性进行赋值,(n5->__forwarding->n5)这里可以看出block修饰的变量生成的结构体对象中`forwarding`指针指向了变量本身
总结:

  • Block会自动捕获局部变量,局部静态变量,__block修饰的变量
  • Block不会捕获全局变量,静态全局变量
  • Block对局部变量是在生成结构体对象时会生成属性并进行值拷贝,即值捕获。
  • Block对局部静态变量会在生成结构体对象时生成对应指针类型的属性,并将指针指向局部静态变量本身,即指针捕获
  • __block修饰的局部变量会转换为结构体对象,在生成的结构体对象中有属性**__forwarding**,该属性指向局部变量本身

    4、栈上的Block与堆上的Block使用上有什么区别?