为什么这样使用 dispatch_block_t 不安全?

Why is it not safe to use dispatch_block_t like this?

为什么这样使用dispatch_block_t不安全?

我正在阅读dispatch_block_t的官方评论,我发现了以下代码,我不明白哪里错了?为什么不安全?有人可以告诉我吗?我很感激。希望详细给我解释一下

#ifdef __BLOCKS__
/*!
 * @typedef dispatch_block_t
 *
 * @abstract
 * The type of blocks submitted to dispatch queues, which take no arguments
 * and have no return value.
 *
 * @discussion
 * When not building with Objective-C ARC, a block object allocated on or
 * copied to the heap must be released with a -[release] message or the
 * Block_release() function.
 *
 * The declaration of a block literal allocates storage on the stack.
 * Therefore, this is an invalid construct:
 * <code>
 * dispatch_block_t block;
 * if (x) {
 *     block = ^{ printf("true\n"); };
 * } else {
 *     block = ^{ printf("false\n"); };
 * }
 * block(); // unsafe!!!
 * </code>
 *
 * What is happening behind the scenes:
 * <code>
 * if (x) {
 *     struct Block __tmp_1 = ...; // setup details
 *     block = &__tmp_1;
 * } else {
 *     struct Block __tmp_2 = ...; // setup details
 *     block = &__tmp_2;
 * }
 * </code>
 *
 * As the example demonstrates, the address of a stack variable is escaping the
 * scope in which it is allocated. That is a classic C bug.
 *
 * Instead, the block literal must be copied to the heap with the Block_copy()
 * function or by sending it a -[copy] message.
 */
typedef void (^dispatch_block_t)(void);
#endif // __BLOCKS__

以上代码摘录:

dispatch_block_t block;
if (x) {
    block = ^{ printf("true\n"); };
} else {
    block = ^{ printf("false\n"); };
}
block(); // unsafe!!!

我不明白哪里出了问题?为什么不安全?

这是不安全的,因为块不能保证在它们所在的复合语句结束后存在(即 if 语句的“then”子句和“else”子句。这个问题类似于如果你这样做会发生什么

char *fun(void) {
    char str[] = "hello";
    return str; // !!! returning a pointer to an array
                // about to go out of scope!
}

请注意,像 dispatch_async() 这样的函数会自动将块复制到堆中,因此只要在块超出范围之前分派块,就可以了。

When not building with Objective-C ARC

在 ARC 中是安全的。而且我不知道你为什么会在2019年使用MRC

这里的文档有点混乱,因为它没有针对 ARC 进行全面更新。 Under ARC, this is all done for you automatically.

但至于为什么需要它,Objective-C 块最初分配在堆栈上。这在立即使用和丢弃它们的情况下提高了性能。如果它们逃脱了当前堆栈范围,则需要将它们复制到堆和内存中,就像 object 一样进行管理。这是使用 Block_copy() 或传递 -copy 手动完成的 (pre-ARC)。有关详细信息,请参阅 Blocks Tips & Tricks

但是你的直觉是正确的;只要您使用的是 ARC,发布的代码就可以。如果它是当前 SDK 的一部分,您可能想要打开 Apple 反馈请求更新 header 以使其更加清晰。