Block 在 ARC 下的拷贝
前言
现在有一种说法,是开启arc选项时,已经没有栈上的block了,所以所有的block都不需要copy来拷贝到堆上了。那么这个说法正确与否呢?
结论是这个说法必须是错误的,首先的一点就是arc只是编译器帮助我们给对象自动增加retain,release方法,我们不需要手动的去管理这些成对出现的内存计数方法,其本质上与mrc是一脉相承的,所以arc下必然还是有stack block的。
再次可以简单的写一个例子(就举参考1的要点3):
1 -(id) getBlockArray{
2 int val =10;
3 return [NSArray arrayWithObjects:
4 ^{NSLog(@"blk0:%d",val);},
5 ^{NSLog(@"blk1:%d",val);},nil];
6 }
7 // Other Method
8 id obj = getBlockArray();
9 typedef void (^blk_t)(void);
10 blk_t blk = (blk_t){obj objectAtIndex:0};
11 blk();
这段代码会异常,但是作者解释的不正确。首先我们可以打印一下这个Array,会发现第一个是NSMallocBlock,第二个是NSStackBlock。所以这段代码说明了arc下也是有stack block的。其次这段代码异常是因为array释放的时候,第二个block是栈上面的,对其释放必然会引发异常。
为什么会这样呢,我们接着往下看。
一、NSArray 的生成
1. + (instancetype)arrayWithObjects:(ObjectType)firstObj, ...
- (void)show
{
id obj = [self getBlockArray];
typedef void (^blk_t)(void);
blk_t blk = (blk_t)[obj objectAtIndex:0];
blk();
blk = (blk_t)[obj objectAtIndex:1];
blk();
}
-(id) getBlockArray{
int val =10;
NSArray* a = [NSArray arrayWithObjects:^{NSLog(@"blk0:%d",val);},^{NSLog(@"blk1:%d",val);}, nil];
return a;
}
通过 clang rewrite 看看得到的c++代码,关键地方如下:
static id _I_BlockTest_getBlockArray(BlockTest * self, SEL _cmd) {
int val =10;
NSArray* a = ((NSArray *(*)(id, SEL, ObjectType, ...))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("arrayWithObjects:"), (id)((void (*)())&__BlockTest__getBlockArray_block_impl_0((void *)__BlockTest__getBlockArray_block_func_0, &__BlockTest__getBlockArray_block_desc_0_DATA, val)), ((void (*)())&__BlockTest__getBlockArray_block_impl_1((void *)__BlockTest__getBlockArray_block_func_1, &__BlockTest__getBlockArray_block_desc_1_DATA, val)), __null);
return a;
}
第一个 block 强转成 id 类型,第二个 block 没有。首先 block 在赋值给 id 类型或者 block 类型的成员变量时,block 会拷贝到堆上,所以第一个 block 变成了堆上的 block,但是第二个还是栈上的内存。
整个 getBlockArray 方法在 show 方法中调用,所以用的是同一个 runloop 中带过来的 autoreleasepool,getBlockArray 中的 NSArray* a = [NSArray arrayWithObjects:^{NSLog(@"blk0:%d",val);},^{NSLog(@"blk1:%d",val);}, nil]; 会往这个 autoreleasepool 中添加两个 NSArray (赋值给 a 是添加一次,show 中得到返回值时添加一次),这两个NSArray 不会立即释放,而会在 这个 runloop 结束的时候释放,这个时机会在show的结束设置更外层的调用。而这个时机已经超过了 getBlockArray 的区域,超过这个区域去访问栈内存,所以会crash。
2.一些其他的 NSArray 生成方法说明
+ (instancetype)arrayWithArray:(NSArray<ObjectType> *)array;
效果同上
- (instancetype)initWithArray:(NSArray<ObjectType> *)array;
效果同上
+ (instancetype)arrayWithObject:(ObjectType)anObject;
rewrite后代码参考上面,这个方法的block会转成 id,所以生成 NSArray 中的 block 是堆上的 block。
- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag;
不管flag 是 true 还是 false,都会crash
另外 - (void)addObject:(ObjectType)anObject; 也是因为会转成 id,所以添加的都是堆上的 block。
二、AutoReleasePool
现在给 getBlockArray 加上一个autoreleasepool,看看会发生什么
-(id) getBlockArray{
int val =10;
NSArray* a = nil;
@autoreleasepool {
a = [NSArray arrayWithObjects:^{NSLog(@"blk0:%d",val);},^{NSLog(@"blk1:%d",val);}, nil];
}
return [[NSArray alloc] initWithArray:a copyItems:YES];
}
此时并不会crash,正确的运行了,clang rewrite 一下看看
static id _I_BlockTest_getBlockArray(BlockTest * self, SEL _cmd) {
int val =10;
NSArray* a = __null;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
a = ((NSArray *(*)(id, SEL, ObjectType, ...))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("arrayWithObjects:"), (id)((void (*)())&__BlockTest__getBlockArray_block_impl_0((void *)__BlockTest__getBlockArray_block_func_0, &__BlockTest__getBlockArray_block_desc_0_DATA, val)), ((void (*)())&__BlockTest__getBlockArray_block_impl_1((void *)__BlockTest__getBlockArray_block_func_1, &__BlockTest__getBlockArray_block_desc_1_DATA, val)), __null);
}
return ((NSArray *(*)(id, SEL, NSArray<ObjectType> *, BOOL))(void *)objc_msgSend)((id)((NSArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSArray"), sel_registerName("alloc")), sel_registerName("initWithArray:copyItems:"), (NSArray *)a, ((bool)1));
}
其实并没有多出来什么特殊的处理,我们可以分析分析。
getBlockArray 给 a 赋值的数组,加入到了我们添加的autoreleasepool中,在这个作用域外面,他已经被释放了,其中的栈上的 block 在自己的作用域内释放,没有任何问题。而 a 默认是一个 strong 修饰的变量,在不在使用的时候,编译器会帮我们添加上 release 而释放,所以在函数结束的时候,他也释放了。
另外 copyItem 是 YES,他会往 withArray 的每个 item 都发送 copy 消息,所以函数返回的 NSArray 中每个 item 都是堆上的 block,所以这一次并不会 crash。但是如果 copyItem 是 NO,那么就会出现超过作用域访问栈上 block 的问题,就会 crash 了。
三、字面量
还有一种生成 NSArray 的方式是:
NSArray* a = @[^{NSLog(@"blk0:%d",val);},^{NSLog(@"blk1:%d",val);}];
看看对应的 rewrite 代码是什么样的:
static id _I_BlockTest_getBlockArray(BlockTest * self, SEL _cmd) {
int val =10;
NSArray* a = __null;
a = ((NSArray *(*)(Class, SEL, const ObjectType *, NSUInteger))(void *)objc_msgSend)(objc_getClass("NSArray"), sel_registerName("arrayWithObjects:count:"), (const id *)__NSContainer_literal(2U, ((void (*)())&__BlockTest__getBlockArray_block_impl_0((void *)__BlockTest__getBlockArray_block_func_0, &__BlockTest__getBlockArray_block_desc_0_DATA, val)), ((void (*)())&__BlockTest__getBlockArray_block_impl_1((void *)__BlockTest__getBlockArray_block_func_1, &__BlockTest__getBlockArray_block_desc_1_DATA, val))).arr, 2U);
return a;
}
这里我们看到有 __NSContainer_literal 这个函数,他做了些什么呢?看下他的定义:
struct __NSContainer_literal {
void * *arr;
__NSContainer_literal (unsigned int count, ...) {
va_list marker;
va_start(marker, count);
arr = new void *[count];
for (unsigned i = 0; i < count; i++)
arr[i] = va_arg(marker, void *);
va_end( marker );
};
~__NSContainer_literal() {
delete[] arr;
}
};
多个栈上的 block 作为参数传进去,然后在内部赋值给数组,当 block 作为返回值返回的时候,会被拷贝到堆上,所以,这个数组里面的每个元素都是堆上的 block,所以不会crash。
四、NSDictionary
由于 NSDictionary 会 copy key 但是不 copy object,所以也会出现上面类似的crash情况。
参考1.http://blog.csdn.net/hherima/article/details/38620175
参考2.https://stackoverflow.com/questions/4010578/nsdictionary-dont-copy-values
Block 在 ARC 下的拷贝的更多相关文章
- block 在ARC和非ARC下的不同含义
Block的循环引用 对于非ARC下, 为了防止循环引用, 我们使用__block来修饰在Block中使用的对象: 对于ARC下, 为了防止循环引用, 我们使用__weak来修饰在Block中使用的对 ...
- iOS: ARC和非ARC下使用Block属性的问题
1. Block的声明和线程安全 Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的,可以参考之前的文章(iOS: 非AR ...
- ARC下的block导致的循环引用问题解析
ARC下的block导致的循环引用问题解析 更详细细节请参考 http://blog.sina.com.cn/s/blog_8c87ba3b0101m599.html ARC下,copy到堆上的blo ...
- IOS开发 ARC和非ARC下使用Block属性的问题
1. Block的声明和线程安全 Block属性的声明,首先需要用copy修饰符,因为只有copy后的Block才会在堆中,栈中的Block的生命周期是和栈绑定的,可以参考之前的文章(iOS: 非AR ...
- xcode arc 下使用 block警告 Capturing [an object] strongly in this block is likely to lead to a retain cycle” in ARC-enabled code
xcode arc 下使用 block警告 Capturing [an object] strongly in this block is likely to lead to a retain cyc ...
- 八.OC基础加强--1.autorelease的用法 2.ARC下内存管理 3.分类(category)4.block的学习
1.autorelease的用法 1.自动释放池及autorelease介绍 (1)在iOS程序运行过程中,会创建无数个池子,这些池子都是以栈结构(先进后出)存在的. (2)当一个对象调用auto ...
- __block在ARC和非ARC下有什么不同
一般在block中修改变量都需要事先加block进行修饰.在非arc中,block修饰的变量的引用计算是不变的.在arc中,会引用到,并且计算+1:非arc下可使用(arc直接使用__weak即可) ...
- ARC下需要注意的内存管理
ARC下需要注意的内存管理 2016/04/03 · iOS开发 · 内存管理 分享到:1 原文出处: 一不(@luoyibu) 之前发了一篇关于图片加载优化的文章,还是引起很多人关注的,不过也 ...
- 理解 ARC 下的循环引用
本文由 伯乐在线 - nathanw 翻译,dopcn 校稿.未经许可,禁止转载!英文出处:digitalleaves.com.欢迎加入翻译组. ARC 下的循环引用类似于日本的 B 级恐怖片.当你刚 ...
随机推荐
- 【转+修改】容联云通讯api调用短信发送调用
转自 https://my.oschina.net/u/1995134/blog/814540 需要荣联云通讯 的 相对应SDKjar包. CCP_REST_SMS_SDK_JAVA_v2.6.3 ...
- prority_queue自定义类型使用
struct Tower{ Tower(int h, int p){ height = h; pos = p; } bool operator < (Tower &t) { if (he ...
- [Python 模块] logging模块、Logger类
logging模块: 标准库里面的logging模块,在前面学习线程安全时曾用来解决print被打断的问题,这里会介绍logging模块的功能. logging模块是线程安全的,不需要客户做任何特殊的 ...
- LayIM.AspNetCore Middleware 开发日记(七)Asp.Net.Core.SignalR闪亮登场
前言 前几篇介绍了整个中间件的构成,路由,基本配置等等.基本上没有涉及到通讯部分.不过已经实现了融云的通讯功能,由于是第三方的就不在单独去写.正好.NET Core SignalR已经出来好久了, ...
- 转:日志组件logback的介绍及配置使用方法
转自:http://blog.csdn.net/zgmzyr/article/details/8267072 一.logback的介绍 Logback是由log4j创始人设计的又一个开源日志组件.lo ...
- echo图片延迟加载js
插件描述:和 Lazy Load 一样,Echo.js 也是一个用于图像延迟加载 JavaScript.不同的是 Lazy Load 是基于 jQuery 的插件,而 Echo.js 不依赖于 jQu ...
- ovs加dpdk在日志中查看更多运行细节的方法
想查看更多dpdk+ovs的更多运行细节,可以采用以下方法,增加更多运行日志. 在终端输入: ovs-appctl vlog/set dpdk:file:dbg ovs-appctl vlog/set ...
- 多种移动平均计算总结(MA,EMA,SMA,DMA,TMA,WMA)
多种移动平均计算总结 股票期货里面经常会遇到这些公式,通达信,同花顺,文华,基本都有.作为一个程序员觉得网上比较的思路不清晰,在此做个总结,一目了然. 一.函数简介 MA(x,n)-移动平均,是最简单 ...
- GBK 文件在 sublime 保存时被强制保存为 utf-8 导致中文乱码, 恢复。
原来在 CoverteToUTF8 的 README.zh_CN.md 文件里就有解决方法,如下: * 问:我的文件被保存为 UTF-8,而且变成了乱码,要如何恢复? 答:请打开这个文件,并确认它的编 ...
- ubuntu 如何进行文件、夹删除等操作
rm [选项] 文件-f, --force 强力删除,不要求确认-i 每删除一个文件或进入一个子目录都要求确认-I 在删除超过三个文件或者递归删除前要求确认-r, -R 递归删除子目录-d, --di ...