关于Block循环引用的问题
Block是什么?
首先探究下Block的实现原理,由于Objective-C是C语言的超集,OC中的NSObject对象其实是由C语言的struct+isa指针实现的,Block的内部实现估计也一样,以下三篇Blog对Block的实现机制做了详细研究
- A look inside blocks: Episode 1
- A look inside blocks: Episode 2
-
A look inside blocks: Episode 3
struct __block_impl { void *isa; int Flags; int Reserved; void *FuncPtr; };
这里的isa可能指向三种类型之一的Block:
- NSGlobalBlock:全局类型Block,在编译器就已经确定,直接放在代码段__TEXT上。
- NSStackBlock:位于栈上分配的Block。
- NSMallocBlock:位于堆上分配的Block。
Block循环引用
由于我们很多行为会导致Block的copy,而当Block被copy时,会对block中用到的对象产生强引用(ARC下)或者引用计数加一(MRC下)。
-
ex.1
@property(nonatomic, readwrite, copy) completionBlock completionBlock; //======================================== self.completionBlock = ^ { if (self.success) { self.success(self.responseData); } } };
对象有一个Block属性,Block属性中又引用了对象的其他成员变量,那么就会对这个变量本身产生强引用,那么变量本身和他自己的Block属性就形成了循环引用。在ARC下需要用weak来修饰self , __weak typeof(self) weakSelf = self; 在MRC下就用__block typeof(self) weakSelf = self;
待续...
Block扩展
下面我们通过观察C函数的生成中间编译代码,来了解Block实现原理
main.m
#include <stdio.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
int age = 10;
void (^block)() = ^{
printf("%d",age);
};
block();//
}
return 0;
}- clang -rewrite-objc main.m之后
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
printf("%d",age);
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 30;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}- 可以看出Block体被单独抽出来成为一个C函数。并且调用的时候是指传递。所以就解释了改变不了age变量的值
int main(int argc, char * argv[]) {
@autoreleasepool {
__weak int age = 10;
void (^block)() = ^{
printf("%d",age);
};
age = 30;
block();//30
}
return 0;
}- clang -rewrite-objc main.m之后
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)+=1;
}
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 10};
void (*block)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age, 570425344));
(age.__forwarding->age) = 30;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
return 0;
}- 此时调用的时候是引用传递。所以就可以改变age变量的值