首页 > 解决方案 > Objective-C 中的块是否总是保证捕获变量?

问题描述

在 Objective-C (Objective-C++) 中是否存在编译器可以检测到块中捕获的变量从未被使用并因此决定首先不捕获变量的情况?

例如,假设您有一个NSArray包含大量可能需要很长时间才能解除分配的项目。您需要访问NSArray主线程上的 ,但一旦完成,您愿意在后台队列中释放它。后台块只需要捕获数组然后立即释放。它实际上不需要做任何事情。编译器能否检测到这一点并“错误地”完全跳过块捕获?

例子:

// On the main thread...
NSArray *outgoingRecords = self.records;
self.records = incomingRecords;

dispatch_async(background_queue, ^{
  (void)outgoingRecords;

  // After this do-nothing block exits, then outgoingRecords
  // should be deallocated on this background_queue.  
});

我是否保证outgoingRecords将始终在该块中捕获并且始终在background_queue?

编辑#1

我将添加更多上下文以更好地说明我的问题:

我有一个包含不可变记录的非常大的 std::vector 的 Objective-C++ 类。这很容易成为 1+ 百万条记录。它们是向量中的基本结构,可在主线程上访问以填充表视图。在后台线程上,可能会将一组不同的数据库记录读入一个单独的向量,该向量也可能非常大。

后台读取发生后,我跳转到主线程以交换 Objective-C 对象并重新填充表。

那时,我根本不关心旧向量或其父 Objective-C 类的内容。没有花哨的析构函数或对象图可拆解,但释放数百兆字节,甚至可能千兆字节的内存不是瞬时的。所以我愿意把它放到一个 background_queue 并在那里进行内存释放。在我的测试中,这似乎工作正常,并让我在主线程上有更多时间在 16 毫秒过去之前做其他事情。

我试图了解我是否可以简单地在“空”块中捕获对象,或者我是否应该执行某种无操作操作(如 call count),以便编译器无法以某种方式对其进行优化。

编辑#2

(我最初试图让问题尽可能简单,但似乎它更微妙。根据下面肯的回​​答,我将添加另一个场景。)

这是另一个不使用 dispatch_queues 但仍然使用块的场景,这是我真正感兴趣的部分。

id<MTLCommandBuffer> commandBuffer = ...

// A custom class that manages an MTLTexture that is backed by an IOSurface.
__block MyTextureWrapper *wrapper = ... 

// Issue some Metal calls that use the texture inside the wrapper.

// Wait for the buffer to complete, then release the wrapper.
[commandBuffer addCompletedHandler:^(id<MTLCommandBuffer> cb) {
  wrapper = nil;
}];

在这种情况下,执行顺序由 Metal 保证。与上面的示例不同,在这种情况下,性能不是问题。相反,支持 的 IOSurfaceMTLTexture正在被回收到 CVPixelBufferPool 中。IOSurface 在进程之间共享,据我所知,MTLTexture似乎并没有增加表面上的 useCount。我的包装类可以。当我的包装类被释放时,useCount 减少,然后 bufferPool 可以自由地回收 IOSurface。

这一切都按预期工作,但由于不确定是否需要“使用”块中的包装器实例以确保它被捕获,我最终得到了像上面这样的愚蠢代码。如果包装器在完成处理程序运行之前被释放,则 IOSurface 将被回收并且纹理将被覆盖。

标签: iosobjective-cmacoscocoaobjective-c++

解决方案


编辑以解决问题编辑:

来自块的 Clang语言规范

在 Block 的复合语句中引用的本地自动(堆栈)变量由 Block 作为 const 副本导入和捕获。捕获(绑定)在块文字表达式评估时执行。

如果编译器可以证明没有实际评估对变量的引用,则不需要编译器捕获变量。 程序员可以通过在块开头的语句中引用变量来强制捕获变量,如下所示

(void) foo;

这在捕获变量有副作用时很重要,就像在 Objective-C 或 C++中一样。

(强调补充。)

请注意,使用此技术可确保引用的对象至少与块一样长,但不保证它将与块一起释放,也不保证由哪个线程释放。


不能保证提交到后台队列的块将是最后一个持有对数组的强引用的代码(即使忽略块是否捕获变量的问题)。

首先,该块实际上可能在提交它的上下文返回并释放其强引用之前运行。也就是说,调用的代码dispatch_async()可以从 CPU 中交换出来,并且块可以首先运行。

但是,即使块运行得比这晚一些,对数组的引用也可能在某个地方的自动释放池中,并且有一段时间没有释放。或者可能在其他地方有一个强引用,最终将被清除但不受您的明确控制。


推荐阅读