经常在一些面试题里面或者其他规范里面看到,block里面不能用self,否则会产生循环引用,但是为什么不能用self,循环引用是什么,如何产生的循环引用,这些总是一知半解。带着问题,查询了一些资料了解了一部分产生原因,在这做一下记录,以防自己忘掉了,如果有大神有不同意见,欢迎提出~~
1.block的内部是什么样的?
LLVM Block_private.h上block的定义如下:
/* Revised new layout. */ struct Block_descriptor { unsigned long int reserved; unsigned long int size; void (*copy)(void *dst, void *src); void (*dispose)(void *); }; struct Block_layout { void *isa; int flags; int reserved; void (*invoke)(void *, ...); struct Block_descriptor *descriptor; /* Imported variables. */ };
可以看出block的结构式Block_layout上的方式,实际上我们可以将它理解成为一个对象,通俗点说,一个block就是一个对象。
2.block的捕获变量
block会自动的捕获变量,例如:
int i = 2;
void (^bloc)(void) = ^{
NSLog(@"%d",i);
};
创建的bloc会讲i的值拷贝到block中,就会生成如下的结构体:
struct Block_layout_imp_bloc {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
int i;
};
这里的i是一个只读变量,因此在block中i是不可以被修改的。
如何才能让i值可以修改呢?就是在i的声明时加入__block的关键字,这样有会生成如下结构体,并且赋值到descriptor字段中:
struct Block_descriptor_imp_i {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
这里可以大致理解为,加了__block修饰符之后,会生成一个i变量的指针,放入到了block的对象中
3.循环引用是如何产生的
既然前面要介绍block的捕获变量,肯定是和这个有关系的。先看下如下的代码:
detailVC* dvc1 = [[detailVC alloc] init];
void (^ bloc)(void) = ^(void){
NSLog(@"%@",dvc1);
};
根据捕获变量的方法,bloc对象会将dvc1指针拷贝到对象中,这样bloc中就含
有一个和dvc1指向地址相同的指针,因此dvc1指向的对象,引用计数器就会+1,这样就完成了bloc中包含有dvc1的引用,这样的话,如果bloc不被销毁,dvc1就不会被销毁。
此时,如果我们将这个block当作成员变量保存在dvc1中,如下:
dvc1.callback = bloc;
那么dvc1中也有了bloc的引用,这样就导致了循环引用,这两个对象谁都销毁不掉。
4.如何避免block导致的循环引用
只要将一方的指针改为弱指针就可以,将detailVC类的callback成员变量加上weak关键字,或者添加如下操作:
detailVC* dvc1 = [[detailVC alloc] init];
__weak detailVC* dvc1w = dvc1;
void (^ bloc)(void) = ^(void){
NSLog(@"%@", dvc1w);
};
这样block拥有的dvc1的指针就是弱指针,不会影响到dvc1的销毁。
写这个主要是为了自己的一些学习计划,还有时间长了有个地方方便看,因为技术有限,有什么不对的地方,还望大神们指出~~
参考资料: