2010年WWDC发布iOS4时Apple对Objective-C进行了一次重要的升级:支持Block。说到底这东西就是闭包,其他高级语音例如Java和C++已有支持,第一次使用Block感觉满简单好用的,但是慢慢也遇到很多坑。本文聊聊ARC和non-ARC下Block使用中的引用循环问题,最近遇到了好几次这种问题,还是深入记录下。先来套题目热热身,貌似能够全部答对的人蛮少的

Block实现原理

首先探究下Block的实现原理,由于Objective-C是C语言的超集,既然OC中的NSObject对象其实是由C语言的struct+isa指针实现的,那么Block的内部实现估计也一样,以下三篇Blog对Block的实现机制做了详细研究:

虽然实现细节看着头痛,不过发现Block果然是和OC中的NSObject类似,也是用struct实现出来的东西。这个是LLVM项目compiler-rt分析的block头文Block_private.h头文件中关于Block的struct声明:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
<span class="line-number" style="margin: 0px; padding: 0px;">10
<span class="line-number" style="margin: 0px; padding: 0px;">11
<span class="line-number" style="margin: 0px; padding: 0px;">12
<span class="line-number" style="margin: 0px; padding: 0px;">13
<span class="line-number" style="margin: 0px; padding: 0px;">14
<span class="line-number" style="margin: 0px; padding: 0px;">15
</span></span></span></span></span></span></span></span></span></span></span></span></span></span></span>
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:

  • _NSConcreteGlobalBlock:全局类型Block,在编译器就已经确定,直接放在代码段__TEXT上。直接在NSLog中打印的类型为__NSGlobalBlock__。
  • _NSConcreteStackBlock:位于栈上分配的Block,即__NSStackBlock__。
  • _NSConcreteMallocBlock:位于堆上分配的Block,即__NSMallocBlock__。

为什么会有这么多种类呢?首先来看全局类型Block,看例子:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
<span class="line-number" style="margin: 0px; padding: 0px;">10
<span class="line-number" style="margin: 0px; padding: 0px;">11
<span class="line-number" style="margin: 0px; padding: 0px;">12
</span></span></span></span></span></span></span></span></span></span></span></span>
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情况下没有差别。

这个Block访问了作用域外的变量d,在实现上就是这个block会多一个成员变量对应这个d,在赋值block时会将方法exmpale中的d变量值复制到成员变量中,从而实现访问。

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
</span></span></span></span></span></span></span>
void example() {
int d = 5;
void (^block)() = ^() {
printf("%d\n", d);
};
block();
}

如果要修改d呢?:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
</span></span></span></span></span></span></span></span></span>
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到堆上分配,这样就不再受栈的限制,可以随意使用啦。例如:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
<span class="line-number" style="margin: 0px; padding: 0px;">10
<span class="line-number" style="margin: 0px; padding: 0px;">11
<span class="line-number" style="margin: 0px; padding: 0px;">12
<span class="line-number" style="margin: 0px; padding: 0px;">13
<span class="line-number" style="margin: 0px; padding: 0px;">14
</span></span></span></span></span></span></span></span></span></span></span></span></span></span>
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情况下正常运行,那么就应该修改为:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
</span></span></span></span></span></span></span>
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下)。

如果遇到这种情况:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
</span></span></span></span></span></span></span></span></span>
@property(nonatomic, readwrite, copy) completionBlock completionBlock;

//========================================
self.completionBlock = ^ {
if (self.success) {
self.success(self.responseData);
}
}
};

对象有一个Block属性,然而这个Block属性中又引用了对象的其他成员变量,那么就会对这个变量本身产生强应用,那么变量本身和他自己的Block属性就形成了循环引用。在ARC下需要修改成这样:

<span class="line-number" style="margin: 0px; padding: 0px;">1
<span class="line-number" style="margin: 0px; padding: 0px;">2
<span class="line-number" style="margin: 0px; padding: 0px;">3
<span class="line-number" style="margin: 0px; padding: 0px;">4
<span class="line-number" style="margin: 0px; padding: 0px;">5
<span class="line-number" style="margin: 0px; padding: 0px;">6
<span class="line-number" style="margin: 0px; padding: 0px;">7
<span class="line-number" style="margin: 0px; padding: 0px;">8
<span class="line-number" style="margin: 0px; padding: 0px;">9
</span></span></span></span></span></span></span></span></span>
@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啦,这就打破了循环引用。

深入理解block的更多相关文章

  1. 对于block的理解,block的面试题

    1.block跟swift中的闭包(closure)基本一样,都常用于值的回调,特别是在多线程的网络请求回调中,使用起来极为方便. 2.block的开头是"^",接着是由小括号所报 ...

  2. 理解block和inode

    什么是block和inode? 定义:block就像是杯子 inode就像是杯子的编号,因为杯子太多了 1.根据文件的大小,在磁盘中储存时会占用一个或多个block:那么究竟多大的文件会使用一个blo ...

  3. 理解 block

    开始:Block 简介 Block 是 iOS 4.0 和 Mac OSX 10.6 引入的一个新特性. Block 可以极大的简化代码. 他们可以帮助你减少代码, 减少对代理的依赖, 并且写出更加简 ...

  4. 从汇编代码理解 Block 的内存结构

    ❓ 在断点调试 iOS 程序碰到 block 作为函数的形参时,如果想知道该 block 本身的函数签名信息和函数体地址时,有哪些办法?

  5. Block使用

    1.对block的理解 >  block是iOS4.0之后出现的,是仿照java中匿名函数所创造的,它是c级别的语法,效率比协议-代理高 >  block的是一个匿名函数(没有名字的函数) ...

  6. 理解 Cinder 架构 - 每天5分钟玩转 OpenStack(45)

    从本节开始我们学习 OpenStack 的 Block Storage Service,Cinder 理解 Block Storage 操作系统获得存储空间的方式一般有两种: 通过某种协议(SAS,S ...

  7. Block、委托、回调函数原理剖析(在Object C语境)——这样讲还不懂,根本不可能!

    开篇:要想理解Block和委托,最快的方法是搞明白“回调函数”这个概念. 做为初级选手,我们把Block.委托.回调函数,视为同一原理的三种不同名称.也就是说,现在,我们把这三个名词当成一回事.在这篇 ...

  8. Block介绍(二)内存管理与其他特性

    我们在前一章介绍了block的用法,而正确使用block必须要求正确理解block的内存管理问题.这一章,我们只陈述结果而不追寻原因,我们将在下一章深入其原因. 一.block放在哪里 我们针对不同情 ...

  9. Magento Block设计分析(深入分析)

    Magento中Block是一个很重要的组件,它在Block中充当非常重要的角色,下面我们来分析一下Magento中Block是怎样设计的,我们应该怎样使用这个重要的角色. 1.Magento Blo ...

随机推荐

  1. 又来了,SDE非直连

    现在sde都推荐直连了,没想到还是遇到了非直连. 用10.1默认是直连,但还支持非直连. 安装半天始终无法启动.系统差点崩了. 原来是sde用户密码过期锁定了,好烦. C:\Program Files ...

  2. REST Security with JWT using Java and Spring Security

    Security Security is the enemy of convenience, and vice versa. This statement is true for any system ...

  3. TOMCAT 关闭报错:Tomcat did not stop in time. PID file was not removed

    关闭tomcat的时候,报出如下错误信息: # ./shutdown.sh Using CATALINA_BASE: /opt/openkm-6.3.1-community/tomcat Using ...

  4. C# ?和??运算表达式

    1.单问号(?) 作用:用于给变量设初化的时候,给变量(int类型)赋为null值,而不是0. 例子: public int a; //默认值为0 public int ?b; //默认值为null ...

  5. javascript 函数参数之中的undefined(zz)

    开始看到很多js函数里都带一个undefined的参数,很是疑惑,后来查了查,原来是这样.假如我们定义了一个函数function a(){  if(arg1===undefined) alert(&q ...

  6. JavaScript----分层导航 滚动事件

    分层导航 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3 ...

  7. JS小练习 留言功能

    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...

  8. [转] java书籍(给Java程序猿们推荐一些值得一看的好书 + 7本免费的Java电子书和教程 )

    7本免费的Java电子书和教程 1. Thinking in Java (Third Edition) 本书的作者是Bruce Eckel,它一直都是Java最畅销的免费电子书.这本书可以帮助你系统的 ...

  9. C++之路进阶——poj2104(K-th Number)

    K-th Number Time Limit: 20000MS   Memory Limit: 65536K Total Submissions: 44537   Accepted: 14781 Ca ...

  10. Could not create the view: An unexpected exception was thrown.问题解决

    Could not create the view: An unexpected exception was thrown.问题解决 今天打开Myeclipse10的时候,发现server窗口出现一堆 ...