GCD:嵌套dispatch_async时__block对象的一个内存陷阱
2014-05-16 11:15
375 查看
http://www.cnblogs.com/sunnyxx/archive/2012/10/31/2742326.html
下面的代码在MRC情况下发生,而在ARC情况下不发生,原因很简单nslog 弱引用了。
啥也不说,先上代码:
dispatch_async(whatever_queue, ^{
NSNumber* number = nil;
number = @123;
dispatch_async(main_queue,
^{
NSLog(@"%@", number);
});
});
嗯,很简单,目的很明确,异步生成个数字并在主线程中使用
现在有一个需求,这个number和io操作有关,我想用一个同步串行queue的方式保证io的“加锁”操作
ok 很简单 只要把number的取得dispatch_sync倒一个serial的queue就好了,like:
熟悉block的一看就能看出点问题,serial_queue那个block在block内修改了外部变量,所以得把number加上__block标记:
这下很符合我的世界观了
乍一看,看上去没啥问题了,运行时挂掉:
*** -[CFNumber respondsToSelector:]: message sent to deallocated instance 0x7411a90
追踪之后发现挂在main_queue的NSLog上,访问number时候number已经被销毁了,why?
------探索之后发现:
__block标记类型的对象会随着声明这个对象的block一同从栈空间拷贝到堆空间
但__block的另一个特点就是不会因block内的引用而增加retain count
直白来讲就是__block对象的生命区间的block被销毁之后、这个对象也会被销毁。
再看刚才的代码
声明number的区间是最外层的那个dispatch_async的block
dispatch_async 之后block被提交到queue中、当然是运行了block的copy
当外层block被运行后,运行到内部的async时不会等到内部async的block运行之后再退出,而是直接结束退出(了解GCD的就很好理解了)
这时,外层的block已经完成使命,被释放了,同时释放的还有那个__block的number
而这时,内层async的block很可能还没有开始执行,等到内层block执行时,用到外层已经释放了的对象,果断报错挂掉了。
如果不加__block就没有这个问题 因为内部block使用的话会把引用的对象retain一次
而面对我遇到的状况就只能找点方法解决了,我有一个简单的方法:
这里用autorelease的原因是因为有可能把这个对象外传给主线程或传给其他地方使用,这时传一个autorelease对象才对
---
总结:这个陷阱可能有更优雅的解决办法,留底分享下。
下面的代码在MRC情况下发生,而在ARC情况下不发生,原因很简单nslog 弱引用了。
啥也不说,先上代码:
dispatch_async(whatever_queue, ^{
NSNumber* number = nil;
number = @123;
dispatch_async(main_queue,
^{
NSLog(@"%@", number);
});
});
嗯,很简单,目的很明确,异步生成个数字并在主线程中使用
现在有一个需求,这个number和io操作有关,我想用一个同步串行queue的方式保证io的“加锁”操作
ok 很简单 只要把number的取得dispatch_sync倒一个serial的queue就好了,like:
dispatch_async(whatever_queue, ^{ NSNumber* number = nil; //同步串行queue dispatch_sync(serial_queue, ^{ number = @123; }); dispatch_async(main_queue, ^{ NSLog(@"%@", number); }); });
熟悉block的一看就能看出点问题,serial_queue那个block在block内修改了外部变量,所以得把number加上__block标记:
dispatch_async(whatever_queue, ^{ __block NSNumber* number = nil;//__block标记 dispatch_sync(serial_queue, ^{ number = @123; }); dispatch_async(main_queue, ^{ NSLog(@"%@", number); }); });
这下很符合我的世界观了
乍一看,看上去没啥问题了,运行时挂掉:
*** -[CFNumber respondsToSelector:]: message sent to deallocated instance 0x7411a90
追踪之后发现挂在main_queue的NSLog上,访问number时候number已经被销毁了,why?
------探索之后发现:
__block标记类型的对象会随着声明这个对象的block一同从栈空间拷贝到堆空间
但__block的另一个特点就是不会因block内的引用而增加retain count
直白来讲就是__block对象的生命区间的block被销毁之后、这个对象也会被销毁。
再看刚才的代码
声明number的区间是最外层的那个dispatch_async的block
dispatch_async 之后block被提交到queue中、当然是运行了block的copy
当外层block被运行后,运行到内部的async时不会等到内部async的block运行之后再退出,而是直接结束退出(了解GCD的就很好理解了)
这时,外层的block已经完成使命,被释放了,同时释放的还有那个__block的number
而这时,内层async的block很可能还没有开始执行,等到内层block执行时,用到外层已经释放了的对象,果断报错挂掉了。
如果不加__block就没有这个问题 因为内部block使用的话会把引用的对象retain一次
而面对我遇到的状况就只能找点方法解决了,我有一个简单的方法:
dispatch_async(whatever_queue, ^{ __block NSNumber* number = nil; dispatch_sync(serial_queue, ^{ number = @123; }); [number retian];//暂时retain使之不被释放 dispatch_async(main_queue, ^{ NSLog(@"%@", number); [number autorelease];//保证手动release }); });
这里用autorelease的原因是因为有可能把这个对象外传给主线程或传给其他地方使用,这时传一个autorelease对象才对
---
总结:这个陷阱可能有更优雅的解决办法,留底分享下。
相关文章推荐
- 在已分配的内存中构造一个对象
- 在预先定义的内存位置构造一个对象
- 在预先定义的内存位置构造一个对象
- 聊聊iOS下block + GCD (Grand Central Dispatch)实现异步非阻塞
- GlobalLock(锁定一个全局内存对象)
- 在已分配的内存中构造一个对象
- 由dispatch_async引起的内存泄露
- vs2005如何看到一个对象的内存分布
- 聊聊iOS下block + GCD (Grand Central Dispatch)实现异步非阻塞
- 聊聊iOS下block + GCD (Grand Central Dispatch)实现异步非阻塞
- 如何在指定的内存区域 new 一个对象
- JAVA内存使用--如何计算一个Java对象占用的字节数
- iOS线程开发之--BLOCK & GCD(Grand Central Dispatch)
- JAVA内存使用--如何计算一个Java对象占用的字节数
- iOS线程开发之--BLOCK & GCD(Grand Central Dispatch)
- 一个简单的Delphi框架-xDom-1 内存对象
- dispatch_async 与 NSThread 创建一个任务(更新界面)
- JAVA内存使用--如何计算一个Java对象占用的字节数
- 在预先定义的内存位置构造一个对象
- 今天遇到一个关于对象和对象方法内存分配的有趣的问题