Loops ARC似乎超越了在循环中创建和调度的块中引用的对象

Loops ARC似乎超越了在循环中创建和调度的块中引用的对象,loops,automatic-ref-counting,objective-c-blocks,release-builds,Loops,Automatic Ref Counting,Objective C Blocks,Release Builds,我试图使用dispatch_async在后台线程上进行一些复杂的计算,但是我在块中使用的对象似乎被过度扩展了。我使用的是ARC,所以我认为我不必太在意保留和释放,但在我的例子中,要么我错过了一些重要的东西,要么ARC超过了对象的范围 只有在以下情况下才会出现问题: 我调用dispatch\u async在for循环中创建一个块 我参照在块外部创建的块中的对象 循环至少进行两次迭代(因此至少创建两个块并将其添加到队列) 使用了版本构建配置(因此可能与某些优化相关) 这似乎无关紧要 它是串行队

我试图使用dispatch_async在后台线程上进行一些复杂的计算,但是我在块中使用的对象似乎被过度扩展了。我使用的是ARC,所以我认为我不必太在意保留和释放,但在我的例子中,要么我错过了一些重要的东西,要么ARC超过了对象的范围

只有在以下情况下才会出现问题:

  • 我调用dispatch\u async在for循环中创建一个块
  • 我参照在块外部创建的块中的对象
  • 循环至少进行两次迭代(因此至少创建两个块并将其添加到队列)
  • 使用了版本构建配置(因此可能与某些优化相关)
这似乎无关紧要

  • 它是串行队列还是并发队列
  • 使用什么样的对象
这个问题不是关于在释放配置(如中)中释放的块,而是块中引用的对象被过度释放

我使用NSURL对象创建了一个小示例:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSURL *theURL = [NSURL URLWithString:@"/Users/"];
    dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(myQueue, ^(){
        NSURL *newURL = [theURL URLByAppendingPathComponent:@"test"];
        NSLog(@"Successfully created new url: %@ in initial block", newURL);
    });

    for (int i = 0; i < 2; i++)
    {
        dispatch_async(myQueue, ^(){
            NSURL *newURL = [theURL URLByAppendingPathComponent:@"test"];
            NSLog(@"Successfully created new url: %@ in loop block %d", newURL, i);
        });
    }
}
调试器在for循环的块中的
URLByAppendingPathComponent
调用处停止

使用并发队列时,失败的调用实际上是调用堆栈中带有_Block_release的
释放
调用:

2013-01-07 23:36:13.291 BlocksAndARC[17230:5f03] *** -[CFURL release]: message sent to deallocated instance 0x10190dd30
(lldb) bt
* thread #6: tid = 0x3503, 0x00007fff885914ce CoreFoundation`___forwarding___ + 158, stop reason = EXC_BREAKPOINT (code=EXC_I386_BPT, subcode=0x0)
    frame #0: 0x00007fff885914ce CoreFoundation`___forwarding___ + 158
    frame #1: 0x00007fff885913b8 CoreFoundation`_CF_forwarding_prep_0 + 232
    frame #2: 0x00007fff808166a3 libsystem_blocks.dylib`_Block_release + 202
    frame #3: 0x00007fff89f330b6 libdispatch.dylib`_dispatch_client_callout + 8
    frame #4: 0x00007fff89f38317 libdispatch.dylib`_dispatch_async_f_redirect_invoke + 117
    frame #5: 0x00007fff89f330b6 libdispatch.dylib`_dispatch_client_callout + 8
    frame #6: 0x00007fff89f341fa libdispatch.dylib`_dispatch_worker_thread2 + 304
    frame #7: 0x00007fff852f0cab libsystem_c.dylib`_pthread_wqthread + 404
    frame #8: 0x00007fff852db171 libsystem_c.dylib`start_wqthread + 13
但这可能只是因为时间稍有不同

我认为这两个错误都表明
theURL
引用的NSURL对象被高估了。但为什么呢?我错过了什么吗?或者这是圆弧和方块组合中的一个错误


我希望发生的是,在
dispatch\u async
调用之前或在
dispatch\u async
的实现中(无论如何:在for循环内,每次
dispatch\u async
-call调用一次),块内引用的每个变量都会被保留,并在块的末尾(但在块中)释放

实际发生的情况似乎是,对于代码中出现的
dispatch\u async
,变量被
retain
ed一次,但是
release
在块的末尾被调用,因此每当执行时,这导致循环中的
release
调用多于
retain
调用

但也许我忽略了什么。有更好的解释吗?我是否以某种方式误用了块或弧,或者这是一个bug

编辑:我尝试了@Joshua Weinberg的建议,将引用变量复制到for循环中的局部变量。它在给定的示例代码中起作用,但在涉及函数调用时不起作用:

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSObject *theObject = [[NSObject alloc] init];

    [self blocksInForLoopWithObject:theObject];
}

-(void)blocksInForLoopWithObject:(NSObject *)theObject
{
    dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);
    for (int i = 0; i < 2; i++)
    {
        NSObject *theSameObject = theObject;
        dispatch_async(myQueue, ^(){
            NSString *description = [theSameObject description];
            NSLog(@"Successfully referenced object %@ in loop block %d", description, i);
        });
    }
}
-(无效)应用程序设计完成启动:(NSNotification*)通知
{
NSObject*theObject=[[NSObject alloc]init];
[self blocksInForLoopWithObject:对象];
}
-(void)blocksInForLoopWithObject:(NSObject*)对象
{
dispatch\u queue\u t myQueue=dispatch\u queue\u create(“几个.blocks.queue”,dispatch\u queue\u SERIAL);
对于(int i=0;i<2;i++)
{
NSObject*SameObject=对象;
调度异步(myQueue,^(){
NSString*description=[相似对象描述];
NSLog(@“已成功引用循环块%d中的对象%@”,说明,i);
});
}
}

那么,为什么它在案例中起作用,而在另一个案例中却不起作用呢?我看不出有什么不同。

当我尝试时,我只是能够复制这个。您的诊断似乎很准确,据我所知,这是一个关于如何复制/保留块范围出错的优化问题。这似乎是值得关注的

尽你所能解决这个问题

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification
{
    NSURL *theURL = [NSURL URLWithString:@"/Users/"];
    dispatch_queue_t myQueue = dispatch_queue_create("several.blocks.queue", DISPATCH_QUEUE_SERIAL);

    dispatch_async(myQueue, ^(){
        NSURL *newURL = [theURL URLByAppendingPathComponent:@"test"];
        NSLog(@"Successfully created new url: %@ in initial block", newURL);
    });

    for (int i = 0; i < 2; i++)
    {
        NSURL *localURL = theURL;
        dispatch_async(myQueue, ^(){
            NSURL *newURL = [localURL URLByAppendingPathComponent:@"test"];
            NSLog(@"Successfully created new url: %@ in loop block %d", newURL, i);
        });
    }
}
-(无效)应用程序设计完成启动:(NSNotification*)通知
{
NSURL*URL=[NSURL URLWithString:@”/Users/“];
dispatch\u queue\u t myQueue=dispatch\u queue\u create(“几个.blocks.queue”,dispatch\u queue\u SERIAL);
调度异步(myQueue,^(){
NSURL*newURL=[URL URL通过附加路径组件:@“测试”];
NSLog(@“已成功创建新url:%@在初始块中”,newURL);
});
对于(int i=0;i<2;i++)
{
NSURL*localURL=URL;
调度异步(myQueue,^(){
NSURL*newURL=[localURL URLByAppendingPathComponent:@“测试”];
NSLog(@“已成功创建新url:%@在循环块%d中”,新url,i);
});
}
}

将其复制到堆栈会强制块每次重新捕获它,并强制执行您预期的内存语义。

为了帮助尝试解决此问题的人员,我能够在我的XCode 4.5版本中使用此简化版本重现此问题,发布配置:

- (id)test {
  return [[NSObject alloc] init];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  id foo = [self test];
  for (int i = 0; i < 2; i++)
  {
    [^(){
      NSLog(@"%@", foo);
    } copy];
  }
  NSLog(@"%@", foo);

  return YES;
}
-(id)测试{
返回[[NSObject alloc]init];
}
-(BOOL)应用程序:(UIApplication*)应用程序使用选项完成启动:(NSDictionary*)启动选项{
id foo=[自测试];
对于(int i=0;i<2;i++)
{
[^(){
NSLog(@“%@”,foo);
}拷贝];
}
NSLog(@“%@”,foo);
返回YES;
}

从分析结果来看,ARC似乎在循环内部的末尾错误地插入了一个版本。

“我希望发生的是,在分派\u async调用之前或在分派\u async的实现中(无论如何:在for循环内部,每次分派\u async调用一次)块内引用的每个变量都被保留,并在块末尾(但在块中)释放。”更具体地说,应该发生的是
dispatch\u async
的实现复制块,当块移动到堆中时,它保留引用的变量。然后,当解除分配块时(即分派完成时),它会释放其引用的变量。我还注意到您正在泄漏队列
myQueue
(未释放),尽管泄漏不会导致您看到的问题。我以前遇到过此问题,但它似乎不再出现在最新的XCode上(4.
- (id)test {
  return [[NSObject alloc] init];
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {

  id foo = [self test];
  for (int i = 0; i < 2; i++)
  {
    [^(){
      NSLog(@"%@", foo);
    } copy];
  }
  NSLog(@"%@", foo);

  return YES;
}