Objective-C 中的块是否始终保证捕获变量?
Is a block in Objective-C always guaranteed to capture a variable?
在 Objective-C (Objective-C++) 中是否存在编译器可以检测到从未使用块中的变量捕获并因此决定不在第一个中捕获变量的任何条件地方?
例如,假设您有一个 NSArray
,其中包含大量可能需要很长时间才能解除分配的项目。您需要在主线程上访问 NSArray
,但是一旦完成,您愿意在后台队列中释放它。后台块只需要捕获数组,然后立即释放。它实际上不需要做任何事情。编译器能否检测到这一点,并 "erroneously" 完全跳过块捕获?
示例:
// 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
我将添加更多上下文以更好地说明我的问题:
我有一个 Objective-C++ class,其中包含非常大的 std::vector of immutable 记录。这很容易就是 1+ 百万条记录。它们是向量中的基本结构,并在主线程上访问以填充 table 视图。在后台线程上,可能会将一组不同的数据库记录读入一个单独的向量,该向量也可能非常大。
后台读取发生后,我跳转到主线程以交换 Objective-C 对象并重新填充 table。
那时,我根本不关心旧向量或其父向量的内容 Objective-C class。没有花哨的析构函数或对象图来拆解,但释放数百兆字节,甚至可能千兆字节的内存并不是即时的。所以我愿意把它放到 background_queue 上,并在那里进行内存重新分配。在我的测试中,这似乎工作正常,并让我在主线程上有更多时间在 16 毫秒过去之前做其他事情。
我想知道我是否可以简单地在 "empty" 块中捕获对象,或者我是否应该进行某种无操作操作(例如调用 count
)这样编译器就无法以某种方式优化它。
编辑 #2
(我原本试图让问题尽可能简单,但似乎比这更微妙。根据下面 Ken 的回答,我将添加另一个场景。)
这里还有一个不使用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保证。与上面的示例不同,在这种情况下性能不是问题。相反,支持 MTLTexture
的 IOSurface 被回收到 CVPixelBufferPool 中。 IOSurface 正在进程之间共享,据我所知,MTLTexture
似乎没有增加表面上的 useCount。我的包装器 class 可以。当我的包装器 class 被释放时,useCount 递减,然后 bufferPool 可以自由回收 IOSurface。
这一切都按预期工作,但我最终得到了像上面这样的愚蠢代码,只是出于不确定我是否需要 "use" 块中的包装器实例以确保它被捕获。如果包装器在完成处理程序运行之前被释放,那么 IOSurface 将被回收并且纹理将被覆盖。
编辑以解决问题编辑:
来自 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++.
(强调已添加。)
请注意,使用此技术可保证引用的对象至少与块一样长,但不保证它会与块一起释放,也不保证由哪个线程释放。
不能保证提交到后台队列的块将是最后一个持有数组强引用的代码(即使忽略块是否捕获变量的问题)。
首先,该块实际上可能 运行 在提交它的上下文 returns 之前并释放其强引用。也就是说,调用 dispatch_async()
的代码可以从 CPU 中换出,而块可以先 运行。
但即使该块 运行 晚于此,对数组的引用也可能位于某处的自动释放池中,并且有一段时间没有释放。或者可能在其他地方有一个强引用,它最终会被清除但不在你的明确控制之下。
在 Objective-C (Objective-C++) 中是否存在编译器可以检测到从未使用块中的变量捕获并因此决定不在第一个中捕获变量的任何条件地方?
例如,假设您有一个 NSArray
,其中包含大量可能需要很长时间才能解除分配的项目。您需要在主线程上访问 NSArray
,但是一旦完成,您愿意在后台队列中释放它。后台块只需要捕获数组,然后立即释放。它实际上不需要做任何事情。编译器能否检测到这一点,并 "erroneously" 完全跳过块捕获?
示例:
// 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
我将添加更多上下文以更好地说明我的问题:
我有一个 Objective-C++ class,其中包含非常大的 std::vector of immutable 记录。这很容易就是 1+ 百万条记录。它们是向量中的基本结构,并在主线程上访问以填充 table 视图。在后台线程上,可能会将一组不同的数据库记录读入一个单独的向量,该向量也可能非常大。
后台读取发生后,我跳转到主线程以交换 Objective-C 对象并重新填充 table。
那时,我根本不关心旧向量或其父向量的内容 Objective-C class。没有花哨的析构函数或对象图来拆解,但释放数百兆字节,甚至可能千兆字节的内存并不是即时的。所以我愿意把它放到 background_queue 上,并在那里进行内存重新分配。在我的测试中,这似乎工作正常,并让我在主线程上有更多时间在 16 毫秒过去之前做其他事情。
我想知道我是否可以简单地在 "empty" 块中捕获对象,或者我是否应该进行某种无操作操作(例如调用 count
)这样编译器就无法以某种方式优化它。
编辑 #2
(我原本试图让问题尽可能简单,但似乎比这更微妙。根据下面 Ken 的回答,我将添加另一个场景。)
这里还有一个不使用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保证。与上面的示例不同,在这种情况下性能不是问题。相反,支持 MTLTexture
的 IOSurface 被回收到 CVPixelBufferPool 中。 IOSurface 正在进程之间共享,据我所知,MTLTexture
似乎没有增加表面上的 useCount。我的包装器 class 可以。当我的包装器 class 被释放时,useCount 递减,然后 bufferPool 可以自由回收 IOSurface。
这一切都按预期工作,但我最终得到了像上面这样的愚蠢代码,只是出于不确定我是否需要 "use" 块中的包装器实例以确保它被捕获。如果包装器在完成处理程序运行之前被释放,那么 IOSurface 将被回收并且纹理将被覆盖。
编辑以解决问题编辑:
来自 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++.
(强调已添加。)
请注意,使用此技术可保证引用的对象至少与块一样长,但不保证它会与块一起释放,也不保证由哪个线程释放。
不能保证提交到后台队列的块将是最后一个持有数组强引用的代码(即使忽略块是否捕获变量的问题)。
首先,该块实际上可能 运行 在提交它的上下文 returns 之前并释放其强引用。也就是说,调用 dispatch_async()
的代码可以从 CPU 中换出,而块可以先 运行。
但即使该块 运行 晚于此,对数组的引用也可能位于某处的自动释放池中,并且有一段时间没有释放。或者可能在其他地方有一个强引用,它最终会被清除但不在你的明确控制之下。