在块内,__block 变量和静态变量之间的实际区别是啥?
Posted
技术标签:
【中文标题】在块内,__block 变量和静态变量之间的实际区别是啥?【英文标题】:Within a block, what is the practical difference between a __block variable and a static variable?在块内,__block 变量和静态变量之间的实际区别是什么? 【发布时间】:2013-01-26 21:41:24 【问题描述】:我想在单个块的多次调用中重用一个对象引用,我很好奇:以下两种方法之间的实际区别是什么?
使用__block
变量:
__block Widget *widget = [self buildNewWidget];
for(Gadget *gadget in self.gadgets)
[self useGadget:gadget withCallback:^
if([widget isBroken])
widget = [self buildNewWidget];
gadget.widget = widget;
];
使用static
变量:
for(Gadget *gadget in self.gadgets)
[self useGadget:gadget withCallback:^
static Widget *widget;
if(!widget || [widget isBroken])
widget = [self buildNewWidget];
gadget.widget = widget;
];
显然,这两个代码块从语义角度来看是不同的,但(实际上)我相信它们做的是相同的基本工作。我的猜测是,从内存管理角度、性能角度或其他角度来看是有区别的。任何能说明这些差异(或解释为什么它们没有不同)的见解都会有所帮助。
【问题讨论】:
现在不能输入完整的答案,但是如果你把它们放在一个将被多次调用的函数中,它们的行为会有所不同(前者每次都会创建一个新的小部件,后者不会)。 @jtbandes 我认为这不是真的;我很想看到更多关于你的意思的解释。基于实验,这两种实现都有效地“记住”了最后一个构建的小部件并保持引用直到调用下一个块。我错过了什么吗? 下一个块,是的——但封闭范围也很重要。__block Widget *
变量是局部范围的,但 static Widget *
是文件的全局变量(即使它在块外不可见)。
对不起,我现在明白你的意思了。这也是拉米回答他的方向。你是完全正确的——如果这是一个可以被多次调用的方法的两个实现,那么行为是非常不同的。好点子。
您的代码似乎假定为 ARC;值得明确说明。
【参考方案1】:
一个例子胜过千言万语:
(是的,这是一个非常简单的例子,但它基本上等同于你所做的......)
for (int i = 0; i < 3; i++)
// Your example encompasses this scope,
// not taking into account that we may execute this code multiple times:
// Call the block
(^
// Every instance/execution of this block will have the same object.
static Obj *o;
// Initialize static object
static dispatch_once_t once;
dispatch_once(&once, ^
o = [Obj new];
);
NSLog(@"Object is: %@", o);
)();
// Output:
// Object is: <Obj: 0x100109fd0>
// Object is: <Obj: 0x100109fd0>
// Object is: <Obj: 0x100109fd0>
for (int i = 0; i < 3; i++)
__block Obj *o = [Obj new];
// Call the block
(^
// This block uses the object from its enclosing scope, which may be different.
NSLog(@"Object is: %@", o);
)();
// Output:
// Object is: <Obj: 0x105100420>
// Object is: <Obj: 0x1003004f0>
// Object is: <Obj: 0x105300000>
【讨论】:
【参考方案2】:出于此答案的目的,假设两个示例都包含在 -(void)useGadgetsOnWidgets ...
中。
假设 ARC,您的应用程序是单线程的并且代码是不可重入的(即useGadgetsOnWidgets
不会调用自身),并且在方法返回后不使用该块,主要区别在于:
使用static
变量,widget
将永远存在。这意味着小部件在对-useGadgetsOnWidgets
的调用中被重用(这可能是好是坏),但也意味着小部件将永远保留。您可以通过将小部件从循环/块中拉出来更改此设置(我也在开始时将其初始化为更类似于 __block
版本:
-(void)useGadgetsOnWidgets
static Widget *widget;
widget = [self buildNewWidget];
for(Gadget *gadget in self.gadgets)
[self useGadget:gadget withCallback:^
if([widget isBroken])
widget = [self buildNewWidget];
gadget.widget = widget;
];
widget = nil;
还有第三种变体,它在一定程度上是线程安全的,并假定在方法返回后不使用该块:
-(void)useGadgetsOnWidgets
Widget *widget = [self buildNewWidget];
Widget ** pWidget = &widget;
for(Gadget *gadget in self.gadgets)
[self useGadget:gadget withCallback:^
if([*pWidget isBroken])
*pWidget = [self buildNewWidget];
gadget.widget = *pWidget ;
];
这似乎比使用static
变量(实际上只是一个全局变量)要好一些,但它仍然很棘手。也不是我想教给新手程序员的技术(但话又说回来,任何类型的多线程也不是)。
编辑:对于您描述的问题,比其中任何一个更好的解决方案是将小部件缓存在self
上的 ivar/property 中:
-(Widget*)workingWidget
// Assuming _cachedWidget is an ivar
if ([_cachedWidget isBroken])
_cachedWidget = [self buildWidget];
return _cachedWidget;
-(void)useGadgetsOnWidgets
for(Gadget *gadget in self.gadgets)
[self useGadget:gadget withCallback:^
gadget.widget = [self workingWidget];
];
【讨论】:
您使用 useGadgetsOnWidgets 的“第三种变体”进入了一个奇怪的地方,但我喜欢您的最后一次重构,在使用 [self workingWidget] 编辑之后...感觉就像一种非常“可可”的方式来解决这个问题。 "使用静态变量,小部件将永远存在。" - 这是否意味着它占用的内存空间与 __block 重用的变量完全不同?您知道这种差异是否会对性能产生影响吗? 是的。static
使其有效地成为全局变量,__block
使其以可以复制到堆的方式包装。 (当然,Widget
本身并没有什么不同;只是变量的位置不同。)static
应该稍微快一些,因为包装了访问__block
的代码无论变量是否被复制到堆中,变量都需要工作,因此它要经过一个额外的间接层。我没有做过任何基准测试,但它可能无关紧要(按方法调用的顺序)。【参考方案3】:
正如所写,这两个代码片段的工作方式不同,并且它们具有不同的最终结果。
第二组代码是等待发生的故障。如果由于使用了静态变量,此代码同时在两个不同的线程上运行,它将失败。此代码也将失败,因为您从未初始化静态变量。第一次到达if
语句时,应用程序可能会崩溃。
由于循环的每次迭代似乎都依赖于widget
的当前值,因此您需要在循环之前初始化一个局部变量。由于需要在块内修改此变量,因此您需要将变量设为__block
变量。这意味着您的第一组代码是正确的代码。
【讨论】:
老实说,我不记得 static 是否设置为 nil。我从不依赖这些东西。显式初始化变量既简单又清晰。即使变量被初始化,它仍然是脆弱的代码,不是线程安全的。 因为如果它被初始化为 nil ,它只会在多线程中变得不安全。相反,如果他第一次调用它,因为 !widget 为 true,它将被分配。 @RamyAlZuhouri 如果静态没有显式初始化为nil
,则此代码的第二次使用将从上一次使用该代码的最后一个值开始。这是非常不可能的。
他可能想一直使用同一个小部件,除非它坏了。
为了让这个对话集中在正轨上,让我们假设我可以保证(比如通过互斥锁或其他实现细节)这些块永远不会同时执行。静态指针被隐式初始化为 nil。无论哪个块首先执行,都会首先调用“buildNewWidget”,这对于我的实现来说是完全可以接受的。【参考方案4】:
__block 使变量在块内可用,就像它是全局的一样。如果你在块内使用它,它不会被复制,而是被引用,因为它就像全局一样,它仍然是活动的。但是下次你调用该代码块时,另一个变量将被压入堆栈。
static 使变量仅在范围内可见,并且它在程序的整个执行过程中仍然存在。但是,如果您再次调用该代码块,变量将是相同的。
【讨论】:
static
与其他变量一样,仅在定义它的大括号内可见。在这种情况下,static
仅在回调块内可见。也许这就是你所说的“功能”。
我认为这不太正确 - 范围内的任何变量都可以在块内使用,但如果没有 __block
,则会对该变量进行复制,因此在块内对其进行更改'不反映在块之外。
当您考虑多次调用这两个代码块时会发生什么时,肯定会有很大的不同,就像它们是可以一次又一次调用的方法的两个不同实现一样。感谢您说明这一根本区别,理解这一点很重要。
@CarlVeazey 如果没有__block
关键字,尝试修改块内的变量是错误的。没有复制。
@rmaddy 你是对的,感谢您的更正!不过,您不需要__block
来引用块范围内的变量,尽管看起来答案已经更新以反映这一点。以上是关于在块内,__block 变量和静态变量之间的实际区别是啥?的主要内容,如果未能解决你的问题,请参考以下文章