OGeek|极客世界-中国程序员成长平台

标题: ios - Objective-C 中的 block 是否总是保证捕获变量? [打印本页]

作者: 菜鸟教程小白    时间: 2022-12-12 14:59
标题: ios - Objective-C 中的 block 是否总是保证捕获变量?

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

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

例子:

// 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 将始终在该 block 中被捕获,并且始终在 background_queue 上被释放?

编辑#1

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

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

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

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

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

编辑#2

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

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

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 保证。与上面的示例不同,在这种情况下,性能不是问题。相反,支持 MTLTexture 的 IOSurface 正在被回收到 CVPixelBufferPool 中。 IOSurface 在进程之间共享,据我所知,MTLTexture 似乎不会增加表面上的 useCount。我的包装类可以。当我的包装类被释放时,useCount 减少,然后 bufferPool 可以自由地回收 IOSurface。

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



Best Answer-推荐答案


编辑以解决问题编辑:

来自 Clang Language Specification for Blocks :

Local automatic (stack) variables referenced within the compound statement of a Block are imported and captured by the Block as const copies. The capture (binding) is performed at the time of the Block literal expression evaluation.

The compiler is not required to capture a variable if it can prove that no references to the variable will actually be evaluated. Programmers can force a variable to be captured by referencing it in a statement at the beginning of the Block, like so:

(void) foo;

This matters when capturing the variable has side-effects, as it can in Objective-C or C++.

(已添加重点。)

请注意,使用这种技术可以保证被引用的对象至少与 block 一样长,但不能保证它将与 block 一起释放,也不保证由哪个线程释放。


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

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

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

关于ios - Objective-C 中的 block 是否总是保证捕获变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/53215266/






欢迎光临 OGeek|极客世界-中国程序员成长平台 (http://ogeek.cn/) Powered by Discuz! X3.4