修改调度队列中父作用域的数据

时间:2012-08-11 13:16:21

标签: objective-c

根据并发编程指南:

  

将块添加到调度队列时,这些值通常必须保留为只读格式。但是,同步执行的块也可以使用前面带有__block关键字的变量将数据返回给父调用范围。

我一直在改变在队列外部创建的变量,但我从未用__block指定它们,所以我想知道究竟何时或为何需要它。或者是实例变量总是固有地可以被块改变,就像它们从幕后分配给它们__block一样?

更新:我也应该添加我正在使用异步队列,而上面说的是变量只能在同步队列中更改(使用__block

2 个答案:

答案 0 :(得分:1)

在块内访问类的实例变量iVar将被编译器解释为self->iVar。因此,块捕获self,但未修改。

我确信__block修饰符也适用于dispatch_async,因此这可能是文档错误。

<强> ADDED

以下示例显示__block变量如何与dispatch_async一起使用:

dispatch_queue_t queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);
__block int total = 0;
printf("address of total: %p\n", &total);

// dispatch some (concurrent) blocks asynchronously:
dispatch_async(queue, ^{
    OSAtomicAdd32(5, &total);
});
dispatch_async(queue, ^{
    OSAtomicAdd32(7, &total);
});

// Wait for all blocks to complete:
dispatch_barrier_sync(queue, ^{ });

printf("address of total: %p\n", &total);
printf("total=%d\n", total);

输出:

address of total: 0x7fff5fbff8f0
address of total: 0x100108198
total=12

可以看到,当执行块时,total从堆栈复制到堆。

<强> ADDED

我刚刚在 Blocks Programming Guide 中找到了这个。它解释了为什么将__block变量用于异步块是没有问题的。

  

__块变量存在于变量的词法范围与声明的所有块和块副本之间共享的存储中   在变量的词法范围内创建。因此,存储将   如果块的任何副本,则在堆栈帧的破坏中存活   在框架内声明超出框架的末尾(for   例如,通过在某处加入队伍以便以后执行)。多   给定词法范围内的块可以同时使用共享   变量

     

作为优化,块存储开始于   堆栈 - 就像块本身一样。如果使用复制块   Block_copy(或在块发送副本时在Objective-C中),   变量被复制到堆中。因此,__block的地址   变量可以随时间变化。

答案 1 :(得分:1)

并发编程指南中的引用是关于在函数或方法中创建的变量。在它们内部创建的变量是在堆栈上创建的,只要函数调用它就会保存它们。换句话说,当函数返回时,其堆栈框架与变量一起被销毁。

Objective-c中的对象是在上创建的,因此它们可以在函数返回后生效,但是当您创建如下行时

MyClass *object = [[MyClass alloc] init];

您正在创建将放置在堆上的对象,但您还要创建变量object,该变量保存指向堆中对象的指针。该变量放在当前方法/函数的堆栈帧上,并在返回后进行处理。如果你没有释放它,你的对象即使功能已经结束也不会被处理掉。

这就是阻止复制父块内部引用的范围变量的原因。它们被复制到块的私有内存中,因为它们可能在函数调用结束时被销毁,如您所知,之后可以使用块。 这就是为什么你需要使用__block说明符来通知块不要从堆栈中复制变量并直接使用它。因为在函数返回时可以销毁这些变量,并发编程指南指出在这种情况下只应使用同步调度,因为它将确保在函数返回之前执行块。 编辑:正确的Martin R指出__block变量并不总是直接从堆栈中使用。正如this documentation所说,它们被视为指针,当复制块时,它们的地址可以从堆栈中更改(如在调度中)。复制的变量放在块和函数之间共享的存储中,以延长变量的生命周期。我认为说明局部变量应该与dispatch_sync一起使用的原因是在非ARC环境变量中不保留*,因此造成使用解除分配对象的危险。出于同样的原因,我认为你不要在静态和实例变量之前使用__block。

实例变量静态变量的变量不会存在于任何函数的堆栈中,因此它们不会被块复制,并且您不会需要__block说明符。只要程序运行,静态变量就会存在,但是当保存该变量的对象被销毁时,实例变量就会被销毁。这就是块具有另一个特性的原因 - 块内引用的所有对象将在块的生命周期内保留,以确保不会意外地释放对象。