在NSDictionary(ARC)中块被释放

Mor*_*ris 14 objective-c ios objective-c-blocks automatic-ref-counting

我正在尝试保留对通过方法传递给我的类的块的引用,以便稍后调用.然而,我遇到了麻烦,保持对它的引用.

我认为,显而易见的方法是将它添加到ivar集合中,所有这些集合都应该保持对其内容的强烈引用.但是当我试图将它拉回来时,它是零.

代码非常简单:

typedef void (^DataControllerCallback)(id rslt);

@interface DataController : NSObject {
    NSMutableArray* queue;
}
- (void) addBlock:(DataControllerCallback)callback;
- (void) functionToBeCalledLater;
@end

@implementation DataController

- (id) init {
    self = [super init];
    if (self != nil) {        
        queue = [NSMutableArray new];
    }
    return self;
}

- (void) addBlock:(DataControllerCallback)callback {
    NSDictionary* toAdd = [NSDictionary dictionaryWithObjectsAndKeys:
        [callback copy], @"callback",
        @"some other data", @"data", nil];
    [queue addObject:toAdd];
}

- (void) functionToBeCalledLater {
    NSDictionary* dict = [queue lastObject];
    NSLog(@"%@", [dict objectForKey:@"data"]; //works
    DataControllerCallback callback = [dict objectForKey:@"callback"]; //this is nil
    callback(@"an arguemnt"); //EXC_BAD_ACCESS
}
Run Code Online (Sandbox Code Playgroud)

发生了什么?


更新:我已经尝试过[callback copy],只是callback插入字典,既不起作用.


更新2:如果我只是将我的块粘贴到NSMutableSet中,只要我打电话copy,我就没事了.它很棒.但如果它在NSDictionary中,则不然.

我实际上通过在创建NSDict之后放置一个断点并且永远不会插入回调来测试它.该描述清楚地读出"1键值对",而不是两个.

我现在用一个专门的类来解决这个问题,它只是一个容器.该callback财产被宣布为strong; 我甚至不需要使用copy.

但问题仍然存在:为什么会发生这种情况?为什么NSDictionary不会存储块?它是否与我瞄准iOS 4.3的事实有关,因此ARC必须作为静态库构建?


更新3:女士们,先生们:我是个白痴.

我在这里介绍的代码显然是实际代码的简化版本; 最特别的是,它将一些键/值对从字典中删除.

如果你存储一个在一个NSDictionary使用[NSDictionary dictionaryWithObjectsAndKeys:],你最好是该死确保这些值的一个不是nil.

其中一个是.

ICYMI,它正在提前终止参数列表.我有一个userInfo类型参数被传递到"添加到队列"方法之一,当然,你可以传入"nil".然后,当我构造字典时,在该参数中查看导致构造函数认为我已经终止了参数列表.@"callback"是字典构造函数中的最后一个值,它从未被存储过.

bbu*_*bum 31

与流行的错误概念相反,ARC 不会自动对作为方法参数传递的块进行反堆叠.它仅在从方法/函数返回块时自动解除堆栈.

即这......

[dict setObject: ^{;} forKey: @"boom"];
Run Code Online (Sandbox Code Playgroud)

...如果dict幸存超出范围并且您尝试使用该块将崩溃(实际上,在这种情况下它不会因为这是一个静态块,但这是一个您不能依赖的编译器细节).

在此处记录:

块如何在ARC中工作?

在ARC模式下向块传递块时块"正常工作",例如在返回中.您不必再调用Block Copy.将堆栈"向下"传递到arrayWithObjects:其他执行保留的方法时,仍需要使用[^ {} copy] .

返回值行为可以自动化,因为返回基于堆的块总是正确的(并且总是返回基于堆栈的块的错误).在作为参数的块的情况下,不可能以非常有效且始终正确的方式自动化行为.

分析仪可能应该警告这种用途.如果没有,请提交错误.

(当我的意思是时,我把堆叠起来.对不起.)


由于以下几个原因,编译器不会自动执行block-as-parameters:

  • 不必要地将块复制到堆可能会显着降低性能
  • 块的多个副本可以显着增加性能损失.

即:

 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);
Run Code Online (Sandbox Code Playgroud)

如果这意味着四个Block_copy()操作和aBlock包含大量捕获状态,那将是一个巨大的潜在打击.

•当天只有这么多小时,参数处理的自动化充满了非明显的边缘情况.如果将来自动处理,可以在不破坏现有代码的情况下完成,因此,将来可能会完成.

即编译器可以生成:

 aBlock = [aBlock copy];
 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);
 doSomethingSynchronous(aBlock);
 [aBlock release];
Run Code Online (Sandbox Code Playgroud)

这不仅会解决block-as-param的问题,而且还会在所有潜在用途中生成块的一个副本.


但问题仍然存在:为什么会发生这种情况?为什么NSDictionary不会存储块?它是否与我瞄准iOS 4.3的事实有关,因此ARC必须作为静态库构建?

那时奇怪的事情正在发生.巧合的是,我上周在基于ARC的应用程序中使用了块值作为值,并且它运行正常.

你有一个方便的小例子吗?