块的NSDictionary作为参数导致溢出

时间:2013-07-20 13:53:15

标签: objective-c objective-c-blocks

我理解块是客观的c对象,并且在使用ARC时可以在没有Block_copy的情况下直接放入NSDictionary。 但是我的代码出现了EXC_BAD_ACCESS错误:

- (void)viewDidLoad
{
    [super viewDidLoad];
    [self method1:^(BOOL result){
        NSLog(@"method1WithBlock finished %d", result);
    }];
}

- (void) method1:(void (^)(BOOL))finish{

    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:^(NSData *rcb){
        finish(YES);
    }, @"success",
                          ^(NSError *error){
                              finish(NO);
                         }, @"failure",  nil];

    [self method2:dict];
}


- (void) method2:(NSDictionary *)dict{
    void (^success)(NSData *rcb) = [dict objectForKey:@"success"];
    success(nil);
}

如果我将method1更改为此,则不会引发任何错误。

- (void) method1:(void (^)(BOOL))finish{
    void (^success)(NSData *)  = ^(NSData *rcb){
        finish(YES);
    };

    void (^failure)(NSError *error) = ^(NSError *error){
        finish(NO);
    };
    NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:success, @"success",
                          failure, @"failure",  nil];
    [self method2:dict];
}

有人可以解释为什么我必须使用自动变量来存储块,然后再将它们放到字典中吗?

我正在使用iOS SDK 6.1。

3 个答案:

答案 0 :(得分:5)

根据"Transitioning to ARC Release Notes",您必须复制存储的块 在字典中(强调我的):

  

在ARC模式下将块传递到堆栈时阻止“正常工作”,例如   在回报中。您不必再调用Block Copy。   将“堆叠”传递到“堆栈”时,您仍然需要使用[^{} copy]   arrayWithObjects: 以及其他保留的方法。

第二种方法“偶然”起作用,因为successfailure是一种 __NSGlobalBlock__而不是需要复制到堆中的“基于堆栈的块”。

答案 1 :(得分:0)

  

我理解块是客观的c对象,在使用ARC时可以直接放在NSDictionary中而不需要Block_copy。

不,它们不是常见的物体。当你创建一个块时,它就在堆栈上,并且当你从堆栈中弹出它的函数时,它与它的保留计数无关。复制它以使其保持活力。

答案 2 :(得分:0)

在将块传递给方法之前,必须先复制块,1)块的存储时间超过调用的持续时间,2)传递给它的参数是普通的对象指针类型(即{{ 1}}或id)而不是块类型。

您的电话就是这种情况。 NSObject *将参数存储在结果字典中,它只是期望普通对象指针参数(类型为dictionaryWithObjectsAndKeys:),并且不知道您是否正在传递块。

我说的第二个条件的原因是因为如果方法参数已经采用块类型(例如对于任何完成处理程序参数),那么该方法已经知道了块的特殊内存管理要求,因此需要如果需要存储块,则负责复制块。在这种情况下,呼叫者不需要担心它。但是,在您的情况下,您将它传递给一个通常的方法,该方法不知道它正在获取块,因此不知道要复制它。所以来电者必须这样做。

  

任何人都可以解释为什么我必须使用自动变量来存储   在将它们放入字典之前阻塞?

关于这一点,你的第二个例子碰巧工作,因为ARC编译器的最新版本对于块是非常保守的,并且只要将它分配给块类型变量就插入副本。但是,ARC规范不保证这一点,并且不保证将来或其他编译器可以使用。你不应该依赖这种行为。