5

我写了一些代码使用objective-c块,但结果让我很困惑。

@interface MyTest : NSObject

@end

@implementation MyTest

- (void)test {
    NSArray *array = [self array1];  // ok
//    NSArray *array = [self array2];// crash
//    NSArray *array = [self array3];// ok again

    dispatch_block_t block0 = (dispatch_block_t)[array objectAtIndex:0];
    block0();

    dispatch_block_t block1 = (dispatch_block_t)[array objectAtIndex:1];
    block1();
}

- (NSArray *)array1 {
    int a = 10;
    NSMutableArray *array = [NSMutableArray array];
    [array addObject:^{
        NSLog(@"block0: a is %d", a);
    }];
    [array addObject:^{
        NSLog(@"block1: a is %d", a);
    }];
    return array;
}

- (NSArray *)array2 {
    int a = 10;

    return [NSArray arrayWithObjects:^{
        NSLog(@"block0: a is %d", a);
    }, ^{
        NSLog(@"block1: a is %d", a);
    }, nil];
}

- (NSArray *)array3 {
    int a = 10;

    return [NSArray arrayWithObjects:^{
        NSLog(@"block0: a is %d", a);
    },[^{
        NSLog(@"block0: a is %d", a);
    } copy], nil];
}
@end

我很困惑:

  1. 为什么array2崩溃?array1 和 array2 之间的真正区别是什么?
  2. 我读过一些文章说块复制会将块从堆栈移动到堆,但是在方法 array1 中,我不复制它并且它仍然有效。在array3中,我只需复制第二个块就可以了。为什么 ?
  3. 使用块时必须在哪里使用副本?

顺便说一句,我在 ARC 下的 Xcode 4.6 中运行代码。谢谢

4

3 回答 3

4

您似乎发现了与编译器不处理的块有关的类型丢失的情况。但我们需要从头开始...

以下内容涉及 ARC 下块的使用。不考虑其他情况(MRC、GC)。

在堆栈而不是堆上创建一些块是一种优化在技术上可以以程序员永远不需要意识到的方式实现。然而,当第一次引入块时,决定优化对用户来说是不透明的,因此引入了blockCopy(). 从那时起,规范和编译器都在发展(并且编译器实际上超出了规范),并且(根据规范)blockCopy() 不再需要它曾经是并且可能不会(因为编译器可能超出规范)别人需要。

如何透明地实施优化?

考虑:

  1. 编译器知道它何时创建堆栈分配块;和
  2. 编译器知道何时将这样的块分配给另一个变量;所以
  3. 编译器能否为每个赋值确定块是否需要移动到堆中?

简单的答案是“是”——在任何任务上移动到堆。但这会否定优化的全部目的-创建一个堆栈块,将其传递给另一个方法,该方法涉及并分配给参数...

简单的答案是“不要尝试” - 介绍blockCopy()并让程序员弄清楚。

更好的答案是“是”——但要聪明地做。在伪代码中,情况是:

// stack allocated block in "a", consider assignment "b = a"
if ( b has a longer lifetime than a )
{
   // case 1: assigning "up" the stack, to a global, into the heap
   // a will die before b so we need to copy
   b = heap copy of a;
}
else
{
   if (b has a block type)
   {
      // case 2: assigning "down" the stack - the raison d'être for this optimisation
      // b has shorter life (nested) lifetime and is explicitly typed as a block so
      // can accept a stack allocated block (which will in turn be handled by this
      // algorithm when it is used)
      b = a;
   }
   else
   {
      // case 3: type loss - e.g. b has type id
      // as the fact that the value is a block is being lost (in a static sense)
      // the block must be moved to the heap
      b = heap copy of a;
   }
}

在引入块时,案例 1 和 3 需要手动插入blockCopy(),而案例 2 是优化得到回报的地方。

但是,正如在较早的答案中所解释的那样,规范现在涵盖了案例 1,而编译器似乎涵盖了案例 3​​,但没有文档证实这一点是已知的。

(顺便说一句,如果您点击该链接,您将看到它包含指向有关该主题的较早问题的链接。那里描述的案例现在自动处理,它是上述案例 1 的示例。)

呸,明白了吗?让我们回到问题中的示例:

  • array1array3并且array4都是存在类型丢失的情况 3 的所有示例。它们也是上一个问题中测试的场景,发现是由当前编译器处理的。它们的工作不是偶然或运气,编译器会显式插入所需的块副本。但是我不知道这在任何地方都有正式记录。
  • array2也是情况 3 的示例,其中存在类型丢失,但它是上一个问题中未测试的变体 - 通过作为变量参数列表的一部分传递的类型丢失。当前编译器似乎无法处理这种情况。所以现在我们知道为什么没有记录案例 3 的处理 - 处理不完整。

请注意,如前所述,可以测试编译器的功能 - 您甚至可以在代码中加入一些简单的测试,以便在测试失败时立即中止应用程序。因此,如果您愿意,您可以根据您所知道的编译器当前自动处理的内容编写代码(到目前为止,所有内容都被认为是接受可变参数函数),并且如果您更新编译器并且替换缺少支持,它将中止您的代码。

希望这有帮助并且有意义!

于 2013-06-08T20:12:59.523 回答
1

所有这三个对我来说都崩溃了(尽管我怀疑第copy一个元素上缺少 aarray3可能是一个疏忽。)如果你希望一个块在创建它的范围内存活,就必须复制它。除非您明确知道某个方法复制了您传入其中的对象,否则您需要自己复制它。

于 2013-06-08T17:21:23.040 回答
0

我尝试了第四个案例,它也可以正常工作:

- (NSArray *)array4 {
    int a = 10;

    return @[ ^{
        NSLog(@"block0: a is %d", a);
    }, ^{
        NSLog(@"block1: a is %d", a);
    }
             ];
}

当然,这与以下内容相同:

- (NSArray *)array4 {
    int a = 10;

    id blocks[] = { ^{
        NSLog(@"block0: a is %d", a);
    }, ^{
        NSLog(@"block1: a is %d", a);
    }
    };
    NSUInteger count = sizeof(blocks) / sizeof(id);

    return [NSArray arrayWithObjects:blocks count:count];
}

所以唯一的问题是“array2”。该实现的关键点是您正在调用arrayWithObject:采用可变数量参数的方法。

似乎只有第一个(命名的)参数被正确复制。没有任何变量参数被复制。如果添加第三个块,问题仍然出现在第二个块上。仅复制第一个块。

因此,似乎使用带有可变参数构造函数的块,实际上只复制了第一个命名参数。没有任何变量参数被复制。

在创建数组的所有其他方法中,每个块都被复制。

顺便说一句 - 我使用 Xcode 4.6.2 使用 ARC 在 Lion (10.7.5) 下使用简单的 OS X 应用程序运行您的代码和我的添加。当在 iOS 6.1 应用程序中使用相同的代码时,我得到相同的结果。

于 2013-06-08T17:33:34.420 回答