深入理解ObjetiveC的Block

NSArray* getBlockArray()
{
    int num = 100;
    ;
    return [[NSArray alloc]initWithObjects:
    ^ { NSLog(@"this is block0 : %d", num);},
    ^ { NSLog(@"this is block1 : %d", num);},
    ^ { NSLog(@"this is block2 : %d", num);},
     nil];
}

int main(int argc, const char * argv[]) 
{
    NSArray *array = getBlockArray();
    void (^blockObject)(void);
    blockObject = [array objectAtIndex:2];
    blockObject();
}

程序会报错--EXC_BAD_ACCESS

通常这可以理解为一个“野指针”错误,访问了内存中不该访问的内容。

问题在哪?从“野指针”错误,我们很直接能想到的就是block对象引用到的地址内容已经不是我们想要的了,简单说就是block无效了。可block是对象类型的啊,为什么放在数组对象中回传失效了呢,加入NSArray的对象本身就应该retain过啊。

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类型: _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的作用已经非常类似。
“The 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。

转载地址: http://www.molotang.com/articles/1691.html

ARC 下内存泄露的那些点
https://www.zybuluo.com/MicroCai/note/67734

你可能感兴趣的:(深入理解ObjetiveC的Block)