Block的引用循环问题 (ARC & non-ARC)
2015-11-12 23:00
357 查看
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
虽然实现细节看着头痛,不过发现Block果然是和OC中的NSObject类似,也是用struct实现出来的东西。这个是LLVM项目compiler-rt分析的block头文Block_private.h头文件中关于Block的struct声明:
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. */ }; |
_NSConcreteGlobalBlock:全局类型Block,在编译器就已经确定,直接放在代码段__TEXT上。直接在NSLog中打印的类型为__NSGlobalBlock__。
_NSConcreteStackBlock:位于栈上分配的Block,即__NSStackBlock__。
_NSConcreteMallocBlock:位于堆上分配的Block,即__NSMallocBlock__。
为什么会有这么多种类呢?首先来看全局类型Block,看例子:
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(); } |
这个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(); } |
1 2 3 4 5 6 78 | void example() { int d = 5; void (^block)() = ^() { d++; printf("%d\n", d); }; block(); printf("%d\n", d); } |
__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到堆上分配,这样就不再受栈的限制,可以随意使用啦。例如:
1 2 3 4 5 6 7 8 9 10 11 1213 | typedef void (^TestBlock)(); TestBlock getBlock() { char e = 'E'; void (^returnedBlock)() = ^{ printf("%c\n", e); }; return returnedBlock; } void example() { TestBlock block = getBlock(); block(); } |
1 2 3 4 5 6 7 | TestBlock getBlock() { char e = 'E'; void (^returnedBlock)() = ^{ printf("%c\n", e); }; return [[returnedBlock copy] autorelease]; } |
Block中的循环引用问题
扯了这么多,回到Block的循环引用问题,由于我们很多行为会导致Block的copy,而当Block被copy时,会对block中用到的对象产生强引用(ARC下)或者引用计数加一(non-ARC下)。如果遇到这种情况:
1 2 3 4 5 6 78 | @property(nonatomic, readwrite, copy) completionBlock completionBlock; //======================================== self.completionBlock = ^ { if (self.success) { self.success(self.responseData); } } }; |
1 2 3 4 5 6 78 | @property(nonatomic, readwrite, copy) completionBlock completionBlock; //======================================== __weak typeof(self) weakSelf = self; self.completionBlock = ^ { if (weakSelf.success) { weakSelf.success(weakSelf.responseData); } }; |
__block typeof(self) weakSelf = self;就表示Block别再对self对象retain啦,这就打破了循环引用。
相关文章推荐
- JSplitPane布局方式的使用
- sed 删除、替换、打印
- C++类中定义的类
- 使用事件总线框架EventBus和Otto
- hdu 4612 Warm up 边双连通分量+树的直径
- C/C++中的max/min函数宏定义问题
- JSP之登录,注册页面(四)
- cardboard sdk for unity 系统分析 - 对像与类
- 原因是未找到“sgen.exe”,或未安装 .NET Framework SDK v2.0
- leetcode day4
- WIN7无法访问共享打印机及文件的解决办法
- 二叉树的基本操作(一)
- 将bitmap转换成任意大小
- 工作学习的三大能力
- poj 1088 滑雪【动态规划】
- JS贪吃蛇开发笔记2
- WIN7下安装LINUX虚拟机
- Apache SSL服务器配置SSL详解
- linux 按时间删除文件
- 美团Android资源混淆保护实践