使用 NSAutoreleasePool 和 NSURLConnection
Posted
技术标签:
【中文标题】使用 NSAutoreleasePool 和 NSURLConnection【英文标题】:Using NSAutoreleasePool with NSURLConnection 【发布时间】:2011-10-16 23:06:59 【问题描述】:我正在尝试按照 XMLPerformance 示例制作我自己的 xml 解析器。到目前为止,我最难让自动释放池工作,我在重新创建池的那一刻就崩溃了。
我将问题缩小到这个测试用例:
PoolCrashTest.h
#import <SenTestingKit/SenTestingKit.h>
@interface PoolCrashTest : SenTestCase
@private
NSURLConnection *connection;
NSAutoreleasePool *downloadAndParsePool;
BOOL done;
@property (nonatomic, retain) NSURLConnection *connection;
@property (nonatomic, assign) NSAutoreleasePool *downloadAndParsePool;
- (void)downloadAndParse:(NSURL *)url;
@end
PoolCrashTest.m
#import "PoolCrashTest.h"
@implementation PoolCrashTest
@synthesize downloadAndParsePool, connection;
- (void)downloadAndParse:(NSURL *)url
done = NO;
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
NSURLRequest *theRequest = [NSURLRequest requestWithURL:url];
self.connection = [[NSURLConnection alloc]
initWithRequest:theRequest delegate:self];
if (connection != nil)
do
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
while (!done);
self.connection = nil;
[downloadAndParsePool release];
self.downloadAndParsePool = nil;
#pragma mark NSURLConnection Delegate methods
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data
[downloadAndParsePool drain];
在此行之后崩溃 ^
self.downloadAndParsePool = [[NSAutoreleasePool alloc] init];
- (void)testPoolCrash
NSURL *dumpURL = [NSURL URLWithString:@"file:///some.xml"];
[NSThread detachNewThreadSelector:@selector(downloadAndParse:)
toTarget:self withObject:dumpURL];
sleep(10);
@end
有人可以解释如何正确清除线程中运行的 NSURLConnection 委托中的自动释放池吗?
我已尝试尽可能地遵循 XMLPerformance...我的目标是 Lion,主要是默认项目设置。
【问题讨论】:
在 didReceiveData 中跳过 drain/alloc pool 是否有效?此外,我认为连接会在您分配和保留(通过属性)时泄漏,然后只释放一次(将属性分配给 nil)。你应该在 alloc/init 之后自动释放它。 Mattias: 是的,我应该提到崩溃发生在 [[NSRunLoop currentRunLoop] runMode] 行,或者更确切地说,它是回溯中可用源代码的最后一行——顶部跟踪是 AutoreleasePoolPage::pop(void*) 方法,这让我觉得我试图释放其他地方创建的其他池。关于连接泄漏,您是对的——那是我草率的复制和粘贴。 【参考方案1】:克雷格说你过度释放你的池是正确的。在非 GC 环境中,release
和 drain
具有相同的效果。在 GC 环境中,release
对任何对象都是无操作,因此必须使用drain
。我只会使用drain
。
然而,NSAutoreleasePool
对象并不是你真正应该成为你的类的属性的东西。如果您将它们的使用限制在词法范围内,它们将最适合您。您可以通过多种方式在上面发布的代码中使用池。
请记住,当您旋转运行循环时,它会在调用中弹出和弹出以在常见模式下运行运行循环;所以你可以这样做:
if (connection != nil)
do
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
beforeDate:[NSDate distantFuture]];
[pool drain];
while (!done);
并且您将耗尽在运行循环的特定轮次中创建的所有自动释放对象。由于调用 run loop 会调用连接的委托回调,所以当这个池耗尽时,将清除委托回调中创建的任何自动释放对象。
如果您对此不满意,可以根据您的委托方法可能完成的工作量,在您的委托方法中放置一个池:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// Do whatever work you want here
[pool drain];
在你的情况下,它会产生大致相同的效果。
我强烈建议您执行上面的第一个示例,并消除您现在拥有的自动释放池行为。将 NSAutoreleasePool
对象保留在单个词法范围内有助于从调试到必要时安全异常处理的一切。
【讨论】:
谢谢,我想我会采用你的方法。我忽略的一件事是我启用了僵尸,这增加了内存使用量。我以为我泄露了什么。 关于池范围的好点 - 我不经常使用它们,不知道它们不应该被 ivar'ed。很有帮助。 :)【参考方案2】:您过度释放池。
我正在重新阅读您的问题以确保我理解,但如果您查看NSAutoreleasePool Documentation,您会发现release
和drain
不应同时使用 . (这与简单地调用两次release
几乎相同。)只使用drain
:
在垃圾收集环境中,不需要自动释放池。但是,您可以编写一个设计为在垃圾收集和引用计数环境中工作的框架。在这种情况下,您可以使用自动释放池向收集器提示收集可能是合适的。在垃圾收集环境中,如有必要,向池发送一条 Drain 消息会触发垃圾收集;但是,发布是无操作的。在引用计数环境中,drain 与 release 具有相同的效果。 因此,通常应该使用 drain 而不是 release。
编辑:This previous question 可能会帮助您了解应该如何在多线程环境中使用周期性耗尽的NSAutoreleasePool
。
【讨论】:
不要认为他在 didReceiveData 中分配和分配新池时过度释放,但在 downloadAndParse 中的释放应该是一种消耗 但是 alloc/init 发生一次,然后有一个释放和一个流失。这是过度释放。 自动释放池 alloc/init 出现在downloadAndParse
和 didReceiveData
中。保留/(释放或排出)计数处于平衡状态,但正如@chris 指出的那样,当您不将自己的自动释放池限制在一个范围内时,当前线程的池堆栈可能会变得混乱。以上是关于使用 NSAutoreleasePool 和 NSURLConnection的主要内容,如果未能解决你的问题,请参考以下文章
在没有 NSAutoReleasePool 的情况下使用 autoReleased 对象?
NSAutoreleasePool 和 @autoreleasepool 块有啥区别?
NSRunLoop 和 NSAutoreleasePool,它们是如何交互的?