关于objective-c阻止和复制的一些问题

时间:2013-06-08 15:17:20

标签: objective-c block

我使用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. 我在使用block时必须使用副本吗?
  4. BTW,我在ARC下的Xcode 4.6中运行代码。感谢

3 个答案:

答案 0 :(得分:4)

您似乎找到了与编译器无法处理的块相关的类型丢失的情况。但我们需要从头开始......

以下内容涉及ARC下块的使用。不考虑其他场景(MRC,GC)。

在堆栈上创建了一些块而不是堆是优化可以在技术上以这样的方式实现,即程序员永远不需要知道它。但是,当首次引入块时,决定优化对用户不透明,因此引入blockCopy()。从那时起,规范和编译器都已经发展(并且编译器实际上超出了规范),并且blockCopy() 不是(通过规范)需要它放置它, 可能(因为编译器可能超出规范)在其他人中需要。

如何透明地实施优化?

考虑:

  1. 编译器在创建堆栈分配块时知道;和
  2. 编译器知道它何时将这样的块分配给另一个变量;所以
  3. 编译器能否为每个分配找出是否需要将块移动到堆中?
  4. 琐碎的回答是“是” - 在任何任务上移动到堆上。但这会否定优化的整个目的 - 创建一个堆栈块,将其传递给另一个方法,该方法涉及并赋值给参数......

    简单的答案是“不要尝试” - 介绍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是优化得到回报的地方。

    然而,正如an earlier answer中的解释,规范现在涵盖了案例1,而编译器似乎涵盖了案例3,但没有文档确认已知。< / p>

    (顺便说一句,如果您按照该链接进行操作,您会看到它包含有关此主题的older question的链接。现在描述的案例现已自动处理,这是上述案例1的示例。)

    Phew,得到了所有这些?让我们回到问题中的例子:

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

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

    希望这有用并且有意义!

答案 1 :(得分:1)

所有这三个崩溃对我来说(虽然我怀疑copy的第一个元素上缺少array3可能是疏忽。)如果你想要它,必须复制一个块比创建它的范围更长。除非您特别知道方法复制了传递给它的对象,否则您需要自己复制它。

答案 2 :(得分: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:方法,该方法接受可变数量的参数。

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

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

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

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