堆block和栈block的区分
0. 问题所在
下面给出一段代码:
- (NSArray*) getBlockArray
{
int num = 916;
return [[NSArray alloc] initWithObjects:
^{ NSLog(@"this is block 0:%i", num); },
^{ NSLog(@"this is block 1:%i", num); },
^{ NSLog(@"this is block 2:%i", num); },
nil];
} - (void) forTest
{
int a = 10;
int b = 20;
} - (void)test
{
NSArray* obj = [self getBlockArray];
[self forTest];
void (^blockObject)(void);
blockObject = [obj objectAtIndex:2];
blockObject();
}
如上两个方法实现的代码并不难理解,其中第三个方法我们要去调用。它会调用第一个方法,并返回一个数组,数组中的元素是block代码块。那么在特定的场景下,调用test会发生crash(闪退)。说明这样的调用存在问题,恐怕能看到的应该就是EXC_BAD_ACCESS错误,通常这可以理解为一个“野指针”错误,访问了内存中不该访问的内容。
问题在哪?从“野指针”错误,我们很直接能想到的就是block对象引用到的地址内容已经不是我们想要的了,简单说就是block无效了。可block是对象类型的啊,为什么放在数组对象中回传失效了呢,加入NSArray的对象本身就应该retain过啊。
问题就在这里,下面我们先来看简单下Block与对象的关系。
1. Block与对象
首先我们先反思几个问题:
- block到底是不是对象?
- 如果是对象,和某个已定义的类的实例对象在使用上是不是一样的?
- 如果不一样,主要的区别是什么?
对于第一个问题,苹果的ObjectiveC官方文档中在“Working with Blocks”明确说明:
“ Blocks are Objective-C objects, which means they can be added to collections like NSArray or NSDictionary. ”
可见, Block是Objective C语言中的对象 。
苹果在block的文档中也提过这么一句:
“ As an optimization, block storage starts out on the stack—just like blocks themselves do. ”
Clang的文档中也有说明:
“ The initial allocation is done on the stack, but the runtime provides a Block_copy function ” (Block_copy在下面我会说)
凭这一点,我们就可以回答剩下的两个问题。 Block对象与一般的类实例对象有所不同,一个主要的区别就是分配的位置不同,block默认在栈上分配,一般类的实例对象在堆上分配。
而这正是导致本文最初提到的那个问题发生的根本原因。Block对象在栈上分配,block的引用指向栈帧内存,而当方法调用过后,指针指向的内存上写的是什么数据就不确定了。但是到此,retain的疑问还是没有解开。
我们想一想Objective C引用计数的原理,retain是对一个在堆中分配内存的对象的引用计数做了增加,执行release操作的时候检查计数是否为1,如果是则释放堆中内存。而对于在栈上分配的block对象,这一点显然有所不同,如果方法调用返回,栈帧上的数据自然会作废处理,不像堆上内存,需要单独release,就算NSArray对block对象本身做了retain也无济于事。
Clang文档中提到:
“ Block pointers may be converted to type id ; block objects are laid out in a way that makes them compatible with Objective-C objects. There is a builtin class that all block objects are considered to be objects of; this class implements retain by adjusting the reference count, not by calling Block_copy . ”
那么要是想如本文开头那样,用一个方法对block数组做初始化是否有可行方案呢。答案是肯定的,不过需要真正了解block的使用,至少要会用Block_copy()和Block_release()。
2. Block的类型和使用
我这里有对某个Block数组的一段Console Log显示,如下:
<__NSArrayI 0x937f240>(
<__NSGlobalBlock__: 0x126750>,
<__NSStackBlock__: 0xbfffc788>,
<__NSMallocBlock__: 0x937f1c0>,
<__NSMallocBlock__: 0x937f1e0>,
<__NSMallocBlock__: 0x937f200>,
<__NSMallocBlock__: 0x937f220>,
<__NSGlobalBlock__: 0x126818>
)
可以看得出,这些对象都是block,而且还分了3种不同的类型。
其实在Clang的文档中,只定义了两个Block类型: _NSConcreteGlobalBlock 和 _NSConcreteStackBlock 。而在Console中的Log我们看到的3个类型应该是处理过的显示,这些字样在苹果的文档和Clang/LLVM的文档中实难找到。通过字面上来看,可以认为 _NSConcreteGlobalBlock对应于 __NSGlobalBlock__ ,_NSConcreteStackBlock对应于 __NSStackBlock__ ,而__NSMallocBlock__则是另一种情况。(实际上也正是如此)
NSGlobalBlock,我们只要实现一个没有对周围变量没有引用的Block,就会显示为是它。而如果其中加入了对定义环境变量的引用,就是NSStackBlock。那么NSMallocBlock又是哪来的呢?malloc一词其实大家都熟悉,就是在堆上分配动态内存时。没错,如果你对一个NSStackBlock对象使用了Block_copy()或者发送了copy消息,就会得到NSMallocBlock。这一段中的几项结论可从代码实验得出。
因此,也就得到了下面对block的使用注意点。
对于Global的Block,我们无需多处理,不需retain和copy,因为即使你这样做了,似乎也不会有什么两样。
对于Stack的Block,如果不做任何操作,就会向上面所说,随栈帧自生自灭。
而如果想让它获得比stack frame更久,那就调用Block_copy(),让它搬家到堆内存上。而对于已经在堆上的block,也不要指望通过copy进行“真正的copy”,因为其引用到的变量仍然会是同一份,在这个意义上看,这里的copy和retain的作用已经非常类似。
“T he runtime provides a Block_copy function which, given a block pointer, either copies the underlying block object to the heap, setting its reference count to 1 and returning the new block pointer, or (if the block object is already on the heap) increases its reference count by 1. The paired function is Block_release , which decreases the reference count by 1 and destroys the object if the count reaches zero and is on the heap. ”
在类中,如果有block对象作为property,可以声明为copy。否则block就随着栈直接消失了
3. 其它
如果注释掉其中看似无关的[self forTest]调用,用当前的Xcode版本(我用的是5.1.1)build后,crash是不会发生的,这看起来很有意思。因为forTest方法本身并没有在逻辑上对数组的构建造成什么影响。
实际上这是因为上一个方法调用的栈帧没有被新的数据覆盖,仍然保留原来block数据的原因所致。这样显然是不安全的,是不能保证block数据可用的。
4. 参考
http://clang.llvm.org/docs/Block-ABI-Apple.html
http://clang.llvm.org/docs/AutomaticReferenceCounting.html?highlight=class
堆block和栈block的区分的更多相关文章
- block存储区域——怎样验证block在栈上,还是堆上
Block存储区域 首先,须要引入三个名词: ● _NSConcretStackBlock ● _NSConcretGlobalBlock ● _NSConcretMallocBlock 正如它们名字 ...
- OC:Block语法、Block使用、Block实现数组排序
Block //定义一个求两个数最大值函数 int maxValue (int ,int); //函数的实现 int maxValue (int a, int b){ return a > b ...
- JVM内存模型——堆(heap)、栈(stack)和方法区(method)
JAVA的JVM的内存可分为3个区:堆(heap).栈(stack)和方法区(method) 堆区:堆内存用于存放由new创建的对象和数组.堆是JVM管理的内存中最大的一块,堆被所有线程共享,目的 ...
- Java中堆内存和栈内存详解2
Java中堆内存和栈内存详解 Java把内存分成两种,一种叫做栈内存,一种叫做堆内存 在函数中定义的一些基本类型的变量和对象的引用变量都是在函数的栈内存中分配.当在一段代码块中定义一个变量时,ja ...
- JAVA内存管理之堆内存和栈内存
我们常常做的是将Java内存区域简单的划分为两种:堆内存和栈内存.这种划分比较粗粒度,这种划分是着眼于我们最关注的.与对象内存分配密切相关的两类内存域.其中栈内存指的是虚拟机栈,堆内存指的是java堆 ...
- java堆内存和栈内存的处理
前段时间学习二叉树在处理删除操作的时候遇到一个头疼的问题:删除节点的时候明明已经置null了可树上该节点依旧存在,还必须执行node.father.left = null;才可以删除node节点,寻找 ...
- CSS 概念 Block Inline Containing block
Block 元素 包括 "block-level box," "block container box," and "block box" ...
- java堆内存与栈内存
java的内存分为两种,堆内存与栈内存: 堆内存用来存放数组和new的对象,比如一个文件,字节流是存放在堆中,栈内存为这个文件开辟一个索引,也就是这个文件的地址,并且保存在栈中.对象由GC处理释放内存 ...
- [转]JVM 内存初学 (堆(heap)、栈(stack)和方法区(method) )
这两天看了一下深入浅出JVM这本书,推荐给高级的java程序员去看,对你了解JAVA的底层和运行机制有比较大的帮助.废话不想讲了.入主题: 先了解具体的概念:JAVA的JVM的内存可分为3个区:堆(h ...
随机推荐
- IBatis.Net XML文件配置
一.添加Provider.config <?xml version="1.0" encoding="utf-8"?> <providers x ...
- PureLayout和Masonry比较
一年前那时我做iOS开发,为了自动布局适配多种屏幕,我一般使用Masonry,后来偶然地在一个视频教程中发现老师使用了UIView+Autolayout(现在作者改名为PureLayout)自动布局, ...
- iOS 开发获取唯一标识
在做流量精灵的时候有这样一个需求,帐户默认需要取手机的imsi 信息.这就有一个好处,即便用户删除我们的应用后从新下载,下次进入时他们还会以同样的身份登陆,并且获得原先纪录.这样不仅对于开发公司人员来 ...
- [蟒蛇菜谱]Python获取任意xml节点的值
# -*- coding: utf-8 -*- import xml.dom.minidom ELEMENT_NODE = xml.dom.Node.ELEMENT_NODE class Simple ...
- CSS第四天总结 更多的属性 圆角 边框图片 段落属性 颜色渐变 盒子阴影
圆角边框: border-radius 一个值时表示四个角用的圆半径,4个值时分别是左上角.右上角.左下角.右下角,单位可以是px和百分比,百分比是半径相对于边框长度的比例 在CSS3中我们终于 ...
- 如何解决xx列不在表中
在连接数据库的程序中常会出现xx列不在表中的问题?那么应该怎么解决呢? 产生此问题的原因有三种: 1.数据表没这个字段2.sql查询没将这个字段查出来3.字段名写错了 还有重要的是一定要检查你的数据库 ...
- iOS开发之Objective-C与JavaScript的交互(转载)
UIWebView是iOS最常用的SDK之一,它有一个stringByEvaluatingJavaScriptFromString方法可以将javascript嵌入页面中,通过这个方法我们可以在iOS ...
- Android Studio Lint 自动检查清除冗余资源
(Android Lint) 辛苦的花了很长的周期 做完了项目. 但是打包完发现 APK 的大小让你瞠目结舌 是不是甚至连自己不知道哪里拷贝过来了代码 和 代码相关的布局文件 资源等, 哪些被使用 哪 ...
- mysql出现启动不了问题
查询日志后是‘ Fatal error: Can't open and lock privilege tables: Table 'mysql.host' doesn't exist’此错误(less ...
- C++实现不能被继承的类——终结类 分类: C/C++ 2015-04-06 14:48 64人阅读 评论(0) 收藏
1. 问题 C++如何实现不能被继承的类,即终结类.Java中有final关键字修饰,C#中有sealed关键字修饰,而C++目前还没有类似的关键字来修饰类实现终结类,需编程人员手动实现. ...