ios - Objective-C 中的 block 是否总是保证捕获变量?
<p><p>在 Objective-C (Objective-C++) 中是否存在编译器可以检测到 block 中捕获的变量从未被使用并因此决定首先不捕获变量的情况? </p>
<p>例如,假设您有一个 <code>NSArray</code>,其中包含大量可能需要很长时间才能解除分配的项目。您需要在主线程上访问 <code>NSArray</code>,但是一旦您完成它,您愿意在后台队列中释放它。后台 block 只需要捕获数组然后立即释放。它实际上不需要做任何事情。编译器能否检测到这一点并“错误地”完全跳过 block 捕获? </p>
<p>例子:</p>
<pre><code>// 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.
});
</code></pre>
<p>我是否保证 <code>outgoingRecords</code> 将始终在该 block 中被捕获,并且始终在 <code>background_queue</code> 上被释放?</p>
<p><strong>编辑#1</strong></p>
<p>我将添加更多上下文以更好地说明我的问题:</p>
<p>我有一个包含不可变记录的非常大的 std::vector 的 Objective-C++ 类。这很容易成为 1+ 百万条记录。它们是向量中的基本结构,可在主线程上访问以填充 TableView 。在后台线程上,可能会将一组不同的数据库记录读入一个单独的向量,该向量也可能非常大。 </p>
<p>一旦发生后台读取,我就跳转到主线程以交换 Objective-C 对象并重新填充表。 </p>
<p>那时,我根本不关心旧向量或其父 Objective-C 类的内容。没有花哨的析构函数或对象图可拆解,但释放数百兆字节,甚至可能千兆字节的内存不是瞬时的。所以我愿意把它放到一个 background_queue 并在那里进行内存释放。在我的测试中,这似乎工作正常,并让我在主线程上有更多时间在 16 毫秒过去之前做其他事情。 </p>
<p>我试图了解我是否可以简单地在“空” block 中捕获对象,或者我是否应该执行某种无操作操作(如调用 <code>count</code>)所以编译器无法以某种方式对其进行优化。 </p>
<p><strong>编辑#2</strong></p>
<p><em>(我最初试图让问题尽可能简单,但似乎它比那更细微。根据下面 Ken 的回答,我将添加另一个场景。)</em></p >
<p>这是另一个不使用 dispatch_queues 但仍然使用 block 的场景,这是我真正感兴趣的部分。</p>
<pre><code>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;
}];
</code></pre>
<p>在这种情况下,执行顺序由 Metal 保证。与上面的示例不同,在这种情况下,性能不是问题。相反,支持 <code>MTLTexture</code> 的 IOSurface 正在被回收到 CVPixelBufferPool 中。 IOSurface 在进程之间共享,据我所知,<code>MTLTexture</code> 似乎不会增加表面上的 useCount。我的包装类可以。当我的包装类被释放时,useCount 减少,然后 bufferPool 可以自由地回收 IOSurface。 </p>
<p>这一切都按预期工作,但由于不确定是否需要“使用” block 中的包装器实例以确保它被捕获,我最终得到了像上面这样的愚蠢代码。如果包装器在完成处理程序运行之前被释放,则 IOSurface 将被回收并且纹理将被覆盖。 </p></p>
<br><hr><h1><strong>Best Answer-推荐答案</ strong></h1><br>
<p><h3>编辑以解决问题编辑:</h3>
<p>来自 Clang <a href="https://clang.llvm.org/docs/BlockLanguageSpec.html" rel="noreferrer noopener nofollow">Language Specification for Blocks</a> :</p>
<blockquote>
<p>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.</p>
<p>The compiler is not required to capture a variable if it can prove
that no references to the variable will actually be evaluated.
<strong>Programmers can force a variable to be captured by referencing it in a
statement at the beginning of the Block, like so</strong>:</p>
<pre><code>(void) foo;
</code></pre>
<p><strong>This matters when capturing the variable has side-effects, as it can
in Objective-C or C++</strong>.</p>
</blockquote>
<p>(已添加重点。)</p>
<p>请注意,使用这种技术可以保证被引用的对象至少与 block 一样长,但不能保证它将与 block 一起释放,也不保证由哪个线程释放。</p>
<hr/>
<p>不能保证提交到后台队列的 block 将是最后一个持有对数组的强引用的代码(即使忽略 block 是否捕获变量的问题)。</p>
<p>首先,该 block 实际上可能在提交它的上下文返回并释放其强引用之前运行。也就是说,调用 <code>dispatch_async()</code> 的代码可以从 CPU 中交换出来,然后 block 可以先运行。</p>
<p>但是,即使 block 运行的时间稍晚,对数组的引用也可能在某个地方的自动释放池中,并且有一段时间没有释放。或者可能在其他地方有一个强引用,最终会被清除,但不受您的明确控制。</p></p>
<p style="font-size: 20px;">关于ios - Objective-C 中的 block 是否总是保证捕获变量?,我们在Stack Overflow上找到一个类似的问题:
<a href="https://stackoverflow.com/questions/53215266/" rel="noreferrer noopener nofollow" style="color: red;">
https://stackoverflow.com/questions/53215266/
</a>
</p>
页:
[1]