首先探究下Block的实现原理,因为Objective-C是C语言的超集,既然OC中的NSObject对象实际上是由C语言的struct+isa指针实现的,那么Block的内部实现估计也同样,如下三篇Blog对Block的实现机制作了详细研究:ide
虽然实现细节看着头痛,不过发现Block果真是和OC中的NSObject相似,也是用struct实现出来的东西。这个是LLVM项目compiler-rt分析的block头文Block_private.h头文件中关于Block的struct声明:svn
1
2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
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_layout中也有一个isa指针,像极了NSobject内部实现struct中的isa指针。这里的isa可能指向三种类型之一的Block:函数
为何会有这么多种类呢?首先来看全局类型Block,看例子:atom
1
2 3 4 5 6 7 8 9 10 11 12 |
void addBlock(NSMutableArray *array) { [array addObject:^{ printf("global block\n"); }]; } void example() { NSMutableArray *array = [NSMutableArray array]; addBlock(array); void (^block)() = [array objectAtIndex:0]; block(); } |
为何addBlock中添加到array中的Block属于全局Block呢?由于它不须要运行时(Runtime)任何的状态来改变行为,不须要放在堆上或者栈上,直接编译后在代码段中便可,就像个c函数同样。这种类型的Block在ARC和non-ARC状况下没有差异。spa
这个Block访问了做用域外的变量d,在实现上就是这个block会多一个成员变量对应这个d,在赋值block时会将方法exmpale中的d变量值复制到成员变量中,从而实现访问。指针
1
2 3 4 5 6 7 |
void example() { int d = 5; void (^block)() = ^() { printf("%d\n", d); }; block(); } |
若是要修改d呢?:code
1
2 3 4 5 6 7 8 9 |
void example() { int d = 5; void (^block)() = ^() { d++; printf("%d\n", d); }; block(); printf("%d\n", d); } |
因为局部变量d和这个block的实现不在同一做用域,仅仅在调用过程当中用到了值传递,因此不能直接修改,而须要加一个标识符__block int d = 5;
,那么block就能够实现对这个局部变量的修改了。若是是这种block标识的变量,在Block实现中再也不是简单的一个成员变量,而是对应一个新的结构体表示这个block变量。block的本质是引入了一个新的Block_byref{$var_name}{$index}结构体,被block关键字修饰的变量就被放到这个结构体中。另外,block结构体经过引入Block_byref{$var_name}{$index}指针类型的成员,得以间接访问到Block的外部变量。这样对Block外的变量访问从值传递转变为引用,从而有了修改内容的能力。对象
正常咱们使用Block是在栈上生成的,离开了栈做用域便释放了,若是copy一个Block,那么会将这个Block copy到堆上分配,这样就再也不受栈的限制,能够随意使用啦。例如:ip
1
2 3 4 5 6 7 8 9 10 11 12 13 14 |
typedef void (^TestBlock)(); TestBlock getBlock() { char e = 'E'; void (^returnedBlock)() = ^{ printf("%c\n", e); }; return returnedBlock; } void example() { TestBlock block = getBlock(); block(); } |
函数getBlock中声明并赋值的returnedBlock,一开始是在栈上分配的,属于NSStackBlock,若是是non-ARC状况下return这个NSStackBlock,那么其实已经被销毁了,在函数中example()使用时就会crash。若是是ARC状况下,getBlock返回的block会自动copy到堆上,那么block的类型就是NSMallocBlock,能够在example()中继续使用。要在Non-ARC状况下正常运行,那么就应该修改成:作用域
1
2 3 4 5 6 7 |
TestBlock getBlock() { char e = 'E'; void (^returnedBlock)() = ^{ printf("%c\n", e); }; return [[returnedBlock copy] autorelease]; } |
扯了这么多,回到Block的循环引用问题,因为咱们不少行为会致使Block的copy,而当Block被copy时,会对block中用到的对象产生强引用(ARC下)或者引用计数加一(non-ARC下)。
若是遇到这种状况:
1
2 3 4 5 6 7 8 9 |
@property(nonatomic, readwrite, copy) completionBlock completionBlock; //======================================== self.completionBlock = ^ { if (self.success) { self.success(self.responseData); } } }; |
对象有一个Block属性,然而这个Block属性中又引用了对象的其余成员变量,那么就会对这个变量自己产生强应用,那么变量自己和他本身的Block属性就造成了循环引用。在ARC下须要修改为这样:
1
2 3 4 5 6 7 8 9 |
@property(nonatomic, readwrite, copy) completionBlock completionBlock; //======================================== __weak typeof(self) weakSelf = self; self.completionBlock = ^ { if (weakSelf.success) { weakSelf.success(weakSelf.responseData); } }; |
也就是生成一个对自身对象的弱引用,若是是倒霉催的项目还须要支持iOS4.3,就用__unsafe_unretained替代__weak。若是是non-ARC环境下就将__weak替换为__block便可。non-ARC状况下,__block变量的含义是在Block中引入一个新的结构体成员变量指向这个__block变量,那么__block typeof(self) weakSelf = self;
就表示Block别再对self对象retain啦,这就打破了循环引用。