“__block”关键字是什么意思?

480

__block 关键字在 Objective-C 中的确切含义是什么?我知道它允许你在块内修改变量,但我想知道...

  1. 它具体告诉编译器什么?
  2. 它还有其他作用吗?
  3. 如果这是它唯一的作用,那么为什么需要它呢?
  4. 文档中是否有相关内容?(我找不到)

3
请查看此处链接(http://developer.apple.com/library/ios/#documentation/cocoa/Conceptual/Blocks/Articles/00_Introduction.html),并阅读“Blocks and variables”部分。 - user866214
1
可能是Objective-C块语法 - 有人能解释一下吗?的重复问题。 - user195488
@Vincent 确认收到,谢谢提醒!我忘记了那个部分,我会看一下的。 - mjisrawi
1
@Code Monkey:我特别问的是关键字,而不是一般的语法。所以不要认为这是重复的。 - mjisrawi
3
@Code Monkey:不,这并不是重复的问题。你提到的那个问题根本没有讨论__block - DarkDust
3
如果有人想知道Objective-C中__block应该如何翻译成Swift: "闭包 [在Swift中] 与块 [在Objective-C中] 具有类似的捕获语义,但存在一个关键区别:变量是可变的,而不是被复制的。换句话说,Objective-C中__block的行为是Swift中变量的默认行为。”来自苹果的书:《使用Swift与Cocoa和Objective-C》(Swift 2.2)。 - Jari Keinänen
8个回答

573

这告诉编译器,任何被标记为__block的变量在其所在的代码块内部使用时必须以特殊方式对待。通常,用于代码块中的变量及其内容会被复制,因此对这些变量所做的任何修改都不会显示在代码块外面。当它们被标记为 __block 时,在代码块内部进行的修改也可以在其外部看到。

有关示例和更多信息,请参见苹果公司的Blocks Programming Topics中的The __block Storage Type

重要示例如下:

extern NSInteger CounterGlobal;
static NSInteger CounterStatic;

{
    NSInteger localCounter = 42;
    __block char localCharacter;

    void (^aBlock)(void) = ^(void) {
        ++CounterGlobal;
        ++CounterStatic;
        CounterGlobal = localCounter; // localCounter fixed at block creation
        localCharacter = 'a'; // sets localCharacter in enclosing scope
    };

    ++localCounter; // unseen by the block
    localCharacter = 'b';

    aBlock(); // execute the block
    // localCharacter now 'a'
}
在这个例子中,localCounterlocalCharacter都在调用块之前被修改了。然而,在块内部,只有对localCharacter的修改是可见的,这要归功于__block关键字。相反,块可以修改localCharacter,并且这种修改在块外部是可见的。

12
非常好的、简洁明了的解释,配有非常有帮助的例子。谢谢! - Evan K. Stone
1
aBlock如何修改localCounter?它似乎只修改了CounterGlobal。谢谢。 - CommaToast
8
它不修改 localCounter,但它会修改 localCharacter。另外,请注意块中 localCounter 的值:它是42,尽管变量在块被调用之前增加了,但是在块被创建时(值被“捕获”时)增加的。 - DarkDust
1
这是一个有帮助的解释 - 但是 - 你能解释一下你的解释中似乎存在矛盾的陈述吗?你在上面说“aBlock modifies...localCounter”,然后在注释中又说“[aBlock] does NOT modify localCounter。” 到底是哪个?如果它“没有被修改”,那么你的答案应该被编辑吗? - Praxiteles
4
一般来说,没有使用 __block 的变量会被以值的方式捕获并打包到块的“环境”中,在创建块时进行。但是使用了 __block 的变量不会被捕获,无论何时它们在块内外使用,都会按原样引用。 - addlistener
它还有其他功能吗?您能否解释一下在NSObject类型的对象之前使用__block的含义? - Robert

30

@bbum在一篇博客文章中深入介绍了块并涉及了__block存储类型。

__block是一个独立的存储类型

像static、auto和volatile一样,__block是一种存储类型。它告诉编译器变量的存储需要进行不同的管理。

...

然而,对于__block变量,块不会进行保留操作。你需要根据需要进行保留和释放。
...

至于使用场景,你会发现__block有时用于避免保留周期,因为它不会保留参数。一个常见的例子是使用self。

//Now using myself inside a block will not 
//retain the value therefore breaking a
//possible retain cycle.
__block id myself = self;

请参考此文章了解有关保留循环问题的更多信息:http://benscheirman.com/2012/01/careful-with-block-based-notification-handlers。在这种特定情况下,使用`__weak`是否也足够了呢?或许会更清晰一些... - Hari Honor
18
在ARC环境下,声称__block可以用来避免强引用循环(也称为保留循环)是完全错误的。由于在ARC中,__block会导致变量被强引用,因此实际上更容易导致强引用循环。 - RK-

16

当您不使用__block时,块会复制变量(按值调用),因此即使您在其他地方修改变量,块也看不到更改。

__block使块保留对变量的引用(按引用调用)。

NSString* str = @"hello";
void (^theBlock)() = ^void() {
    NSLog(@"%@", str);
};
str = @"how are you";
theBlock(); //prints @"hello"

在这两种情况下,您需要使用__block:

  1. 如果您想要在块内修改变量并希望它在外部可见:

    __block NSString* str = @"hello";
    void (^theBlock)() = ^void() {
        str = @"how are you";
    };
    theBlock();
    NSLog(@"%@", str); //prints "how are you"
    
  2. 如果你想要在声明块后修改变量,并且希望块可以看到这些更改:

  3. __block NSString* str = @"hello";
    void (^theBlock)() = ^void() {
        NSLog(@"%@", str);
    };
    str = @"how are you";
    theBlock(); //prints "how are you"
    

我还要提到,用__block标记的自动变量会被编译器转换为一个自动释放的对象,其生命周期与块一样长,可能超出声明范围。此外,它每次都会被创建,因此创建的每个theBlock实例都将引用不同的str变量。 - James Bucanek

11

__block是一种存储限定符,可以有两种用法:

  1. 标记变量存储在原始变量的词法作用域和在该作用域内声明的任何块之间共享的存储中。Clang将生成一个结构来表示此变量,并按引用(而不是按值)使用此结构。

  2. 在MRC中,__block可用于避免块捕获的对象变量被保留。注意这对ARC无效。在ARC中,应改用 __weak

您可以参考苹果文档获取详细信息。


7

__block是一种存储类型,用于使作用域内的变量可变。更确切地说,如果您使用此限定符声明变量,则其引用将传递给块而不是只读副本。有关详细信息,请参见iOS中的块编程


3
希望这能帮到您。
假设我们有以下代码:
{
     int stackVariable = 1;

     blockName = ^()
     {
      stackVariable++;
     }
}

如果代码块内的堆栈变量没有使用__block(存储修饰符)进行声明,那么会出现"variable is not assignable"这样的错误,因为堆栈变量默认是不可变的。

在变量声明之前添加__block可以使其在代码块内可变,例如__block int stackVariable=1;


2
块语言规范中可以看到:
除了新的块类型外,我们还引入了一个新的存储限定符__block来处理局部变量。[testme:在块文字内进行__block声明] __block存储限定符与现有的局部存储限定符auto、register和static互斥。[testme] 由__block修饰的变量表现得好像它们是在分配的存储器中,并且在该变量的最后一次使用后,该存储器会自动恢复。实现可能会选择一种优化,即初始存储为自动的,并且仅在引用块的Block_copy之后“移动”到分配(堆)存储器。这样的变量可以像普通变量一样被改变。
在__block变量是块的情况下,必须假定__block变量驻留在分配的存储器中,因此假定它引用的块也驻留在分配的存储器中(即它是Block_copy操作的结果)。尽管如此,如果实现为块提供初始自动存储,则没有提供Block_copy或Block_release的规定。这是由于潜在的几个线程尝试更新共享变量以及需要同步处理旧值和复制新值的固有竞争条件。这种同步超出了此语言规范的范围。
有关__block变量应编译成什么的详细信息,请参阅块实现规范第2.3节。

你的链接都失效了。 - Ky -
这并不是一个真正的答案,可以进一步完善或删除。谢谢! - Dan Rosenstark

0

这意味着它作为前缀的变量可以在块内使用。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接