【block第三篇】内存管理——如何验证block在栈上,还是堆上

本文探讨Block的内存管理,包括全局Block、栈Block和堆Block的区别。通过实例分析,展示了在ARC和非ARC环境下,`copy`关键字对Block存储位置的影响,以及如何防止程序崩溃。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

-----------------------------------------------欢迎查看block连载博客【专栏】-----------------------------------
【block编程第一篇】block语法                            【block编程第二篇】block捕获变量和对象;
【block编程第三篇】block的内存管理(当前)   【block编程第四篇】block内部实现;
【block编程第五篇】block中如何避免循环引用
------------------------------------------------------------------------------------------------------

Block存储区域

首先,需要引入三个名词:
● _NSConcretStackBlock
● _NSConcretGlobalBlock
● _NSConcretMallocBlock
正如它们名字显示得一样,表明了block的三种存储方式:栈、全局、堆。获取block对象中的isa的值,可以得到上面其中一个,下面开始说明哪种block存储在栈、堆、全局。block为何是个对象,参考 点击打开链接

-----【要点1】:全局block

● 定义在函数外面的block是global类型的
● 定义在函数内部的block,但是没有捕获任何自动变量,那么它也是全局的。比如下面这样的代码
typedef int (^blk_t)(int);
for(...){
    blk_t blk = ^(int count) {return count;};
}
虽然,这个block在循环内,但是blk的地址总是不变的。说明这个block在全局段。 注:针对没有捕获自动变量的block来说,虽然用clang的rewrite-objc转化后的代码中仍显示_NSConcretStackBlock,但是实际上不是这样的。下图可以证明该类型的block是全局的。Xcode5.1.1调试结果

无论ARC与否上图控制台输出是  <__NSGlobalBlock__: 0x10000f280>,这可能是编译器的优化,本人推测,没有求证。所以用clang的-rewrite-objc是不准确的。

--【要点2】:栈block

下面这种情况,在非ARC下是无法编译的,在ARC下可以编译
typedef void (^block_t)() ;
-(block_t)returnBlock{
    __block int add=10;
    return ^{printf("add=%d\n",++add);};
}
这是因为:block捕获了栈上的add自动变量,此时add已经变成了一个结构体,而block中拥有这个结构体的指针。即如果返回block的话就是返回局部变量的指针。而这一点恰是编译器已经断定了。 在ARC下可以编译过,是因为ARC使用了autorelease了。
再说一个场景:

-(block_t)returnBlock{
    __block int add=10;
    block_t blk_h =^{printf("add=%d\n",++add);};
    return blk_h;
}
block_t bb = [self returnBlock];
bb();

这段代码,只是使用了一个自动block变量,可以编过,但是造成程序崩溃了。
如果在返回block的时候加上copy,可以输出正确的数值11

--【要点3】:堆上的block 

有时候我们需要调用block 的copy函数,将block拷贝到堆上。看下面的代码:
-(id) getBlockArray{
    int val =10;
    return [NSArray arrayWithObjects:
        ^{NSLog(@"blk0:%d",val);},
        ^{NSLog(@"blk1:%d",val);},nil];
}

id obj = getBlockArray();
typedef void (^blk_t)(void);
blk_t blk = (blk_t){obj objectAtIndex:0};
blk();
这段代码在最后一行blk()会异常,因为数组中的block是栈上的。因为val是栈上的。解决办法就是调用copy方法。这种场景,ARC也不会为你添加copy,因为ARC不确定,采取了保守的措施:不添加copy。 所以ARC下也是会异常退出。

--【要点4】copy的使用

不管block配置在何处,用copy方法复制都不会引起任何问题。
在ARC环境下,如果不确定是否要copy block尽管copy即可。ARC会打扫战场。
【注意】:
● 在栈上调用copy那么复制到堆上
● 在全局block调用copy什么也不做
● 在堆上调用block 引用计数增加

--【对《Objective-C 高级编程》的挑战】

    笔者用Xcode 5.1.1 iOS sdk 7.1 编译发现:并非《Objective-C 高级编程》这本书中描述的那样,-rewrite-objc这个命令转化的中间代码,并不可靠。
    block在ARC和非ARC有巨大差别:下面笔者用两种方式来验证:
1.通过Xcode调试结果,附图
2.通过变量的地址 int val肯定是在栈上的,我保存了val的地址,看看block调用前后是否变化。输出一致说明是栈上,不一致说明是堆上。
【第一个方法】,最直观.代码如下,很简单。block捕获了变量val( 无论val是否是__block
-(void) stackOrHeap{
    __block int val =10;
    blkt1 s= ^{
        return ++val;};
    s();
    blkt1 h = [s copy];
    h();
}
在非ARC和ARC下,调试结果如下:
可以看到非ARC下一个是stack一个是Malloc。ARC下都是Malloc

【第二个方法】,声明一个局部变量指针。通过指针来看
typedef int (^blkt1)(void) ;
-(void) stackOrHeap{
    __block int val =10;
    int *valPtr = &val;//使用int的指针,来检测block到底在栈上,还是堆上
    blkt1 s= ^{
        NSLog(@"val_block = %d",++val);
        return val;};
    s();
    NSLog(@"valPointer = %d",*valPtr);
}
在ARC下>>>>>>>>>>>该block被会直接生成到堆上了。看log:  val_block = 11 valPointer = 10
在非ARC下>>>>>>>>>该block还是在栈上的。 看log: val_block = 11 valPointer = 11

调用copy之后的结果呢:

-(void) stackOrHeap{
    __block int val =10;
    int *valPtr = &val;//使用int的指针,来检测block到底在栈上,还是堆上
    blkt1 s= ^{
        NSLog(@"val_block = %d",++val);
        return val;};
    blkt1 h = [s copy];
    h();
    NSLog(@"valPointer = %d",*valPtr);
}

在ARC下>>>>>>>>>>>无效果。 val_block = 11 valPointer = 10
在非ARC下>>>>>>>>>确实复制到堆上了。 val_block = 11 valPointer = 10

---【总结】---

用这个表格来表示。捕获变量包括仅仅读取变量和__block这种写变量,两种方式(其实结果是一样的)

【在ARC下】:似乎已经没有栈上的block了,要么是全局的,要么是堆上的。有一个特殊情况:如果仅仅定义了block没有赋值给变量的话,仍是在栈上,比如:
-(id)getBlockArray{
    int val = 10;
    NSArray *arr = [NSArray alloc] initWithObjects:^{NSLog(@"blk0:%d",val);},
                                                   ^{NSLog(@"blk1:%d",val);},
                                                   ^{NSLog(@"blk2:%d",val);},nil];
    return arr;
}
如果使用了array中的block就会crash(使用第1个block才会crash,第0个不会crash,不解啊!)。此时block还在栈上。
【在非ARC下】:存在这栈、全局、堆这三种形式。
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值