深入理解ObjetiveC的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。

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

时间: 2024-12-09 18:53:43

深入理解ObjetiveC的Block的相关文章

通俗理解inline、block、inline-block

display:inline; 内联元素,简单来说就是在同一行显示. display:block; 块级元素,简单来说就是就是有换行,会换到第二行. display:inline-block; 就是在同一行内的块级元素. <a href="#" style="display:inline;width:100px;height:100px;background:#ccc;">链接一</a> <a href="#" st

[论文理解] CBAM: Convolutional Block Attention Module

CBAM: Convolutional Block Attention Module 简介 本文利用attention机制,使得针对网络有了更好的特征表示,这种结构通过支路学习到通道间关系的权重和像素间关系的权重,然后乘回到原特征图,使得特征图可以更好的表示. Convolutional Block Attention Module 这里的结构有点类似与SENet里的支路结构. 对于Channel attention module,先将原feature map分别做global avg pool

OC高级编程——深入block,如何捕获变量,如何存储在堆上

首先先看几道block相关的题目 这是一篇比较长的博文,前部分是block的测试题目,中间是block的语法.特性,block讲解block内部实现和block存储位置,请读者耐心阅读.具备block基础的同学,直接调转到block的实现 下面列出了五道题,看看能否答对两三个.主要涉及block栈上.还是堆上.怎么捕获变量.答案在博文最后一行 //-----------第一道题:-------------- void exampleA() { char a = 'A'; ^{ printf("%

malloc中 heap block 的 blocksize 大小问题

heap block 引发的思考 问题背景: Implicit Free Lists Any practical allocator needs some data structure that allows it to distinguish block boundaries and to distinguish between allocated and free blocks. Most allocators embed this information in the blocks the

初探swift语言的学习笔记十(block)

作者:fengsh998 原文地址:http://blog.csdn.net/fengsh998/article/details/35783341 转载请注明出处 如果觉得文章对你有所帮助,请通过留言或关注微信公众帐号fengsh998来支持我,谢谢! 在前面一些学习中,原本把闭包给理解成了block尽管有很多相似之处,但block还是有他自己的独特之外.近日,在写oc/swift混合编码时,有时候需要swift回调oc,oc回调swift . 因此我把swift中的 block 常见的声明和写

腾讯云数据库团队:PostgreSQL TOAST技术理解

作者介绍:胡彬 腾讯云高级工程师 TOAST是"The Oversized-Attribute Storage Technique"的缩写,主要用于存储一个大字段的值.要理解TOAST,我们要先理解页(BLOCK)的概念.在PG中,页是数据在文件存储中的基本单位,其大小是固定的且只能在编译期指定,之后无法修改,默认的大小为8KB.同时,PG不允许一行数据跨页存储,那么对于超长的行数据,PG就会启动TOAST,具体就是采用压缩和切片的方式.如果启用了切片,实际数据存储在另一张系统表的多个

css3 的vertical-align——我的理解

在下才疏学浅,不足之处,还请多多指教. vertical-align 是css中很有用的一个属性,常用在checkbox和文字的对齐,图片的对齐等位置. 需要注意的是:需要有一个元素属于  inline 或 inline-block(table-cell也可以理解为inline-block水平)水平,其身上的vertical-align属性才会起作用,其实很好理解,为 block 的元素直接独自占一行,不需要对齐. 常见的 vertical-align用法:middle(中线对齐),top(把元

block的定义和内存

1.2 结构体(Struct) 在C语言中,结构体(struct)指的是一种数据结构.结构体可以被声明为变量.指针或数组等,用以实现较复杂的数据结构.结构体同时也是一些元素的集合,这些元素称为结构体的成员(member),且这些成员可以为不同的类型,成员一般用名字访问. 我们来看看结构体的定义: 1 struct tag { member-list } variable-list; struct:结构体关键字. tag:结构体标签. member-list:结构体成员列表. variable-l

Objective-C中的Block

1.相关概念 在这篇笔记开始之前,我们需要对以下概念有所了解. 1.1 操作系统中的栈和堆 注:这里所说的堆和栈与数据结构中的堆和栈不是一回事. 我们先来看看一个由C/C++/OBJC编译的程序占用内存分布的结构: 栈区(stack):由系统自动分配,一般存放函数参数值.局部变量的值等.由编译器自动创建与释放.其操作方式类似于数据结构中的栈,即后进先出.先进后出的原则. 例如:在函数中申明一个局部变量int b;系统自动在栈中为b开辟空间. 堆区(heap):一般由程序员申请并指明大小,最终也由