调试和发布配置之间的不同块行为
Posted
技术标签:
【中文标题】调试和发布配置之间的不同块行为【英文标题】:Different block behavior between debug and release configuration 【发布时间】:2013-06-20 08:22:54 【问题描述】:我的程序运行良好。我用我的生命向你保证,0 错误。自豪地,我尝试将应用程序打包为 .ipa 文件,以便使用 TestFlight 临时分发给我的 beta 测试人员。
程序没有运行。应该发生的动画从未发生过。网络代码中断。美妙地淡出音乐的按钮根本没有任何作用。
事实证明,罪魁祸首是新的闪亮块。当我在模拟器或设备上测试我的程序时,我使用了默认的“调试”构建配置。但是当我将它存档以供分发时(我相信稍后会提交到 App Store),XCode 使用另一种配置,即“发布”。进一步调查,不同之处在于优化级别(您可以在 XCode 的构建设置中找到它):调试使用无(-O0)但发布使用最快、最小(-Os)。我几乎不知道,它是最快的,最小的,而且不起作用(tm)。是的,块在这两种配置之间的行为不同。
所以,我着手解决这个问题。我已经将我即将改变世界的应用程序简化为它的基本框架,如我附在这篇文章中的图片所示。视图控制器有一个初始值为 0 的实例变量 x。如果我们按下 b,它将产生一个线程,将不断检查 x 的值,当 x 变为 1 时更改底部标签。我们可以使用按钮更改 x 的值一个。
这是我的幼稚代码(我正在使用 ARC 顺便说一句):
@implementation MBIViewController
int _x;
- (void)viewDidLoad
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
_x = 0;
- (void)updateLabel
self.topLabel.text = [NSString stringWithFormat:@"x: %d", _x];
- (IBAction)buttonAPressed:(id)sender
_x = 1;
[self updateLabel];
- (IBAction)buttonBPressed:(id)sender
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
while (_x != 1)
// keep observing for value change
dispatch_async(dispatch_get_main_queue(), ^
self.bottomLabel.text = @"b changed me becase x changed!";
);
);
@end
_x 是一个实例变量,因此可以合理地认为该块将使用指向“self”的指针而不是本地副本来访问它。它适用于调试配置!
但它不适用于发布版本。那么也许该块毕竟使用了本地副本?好的,让我们明确地使用 self:
while (self->_x != 1)
// keep observing for value change
在 Release 中也不起作用。好的,让我们直接使用指针访问该死的变量:
int *pointerToX = &_x;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^
while (*pointerToX != 1)
// keep observing for value change
// other codes
);
还是不行。这时我才恍然大悟,智能优化编译器假定在这个多线程世界中,比较的结果不可能改变,所以它可能将其替换为始终为 TRUE 或其他一些巫术。
现在,当我使用它时,事情又开始工作了:
while (_x != 1)
// keep observing for value change
NSLog(@"%d", _x);
所以,为了绕过编译器优化比较,我使用了一个 getter:
- (int)x
return _x;
然后使用该 getter 检查值:
while (self.x != 1)
// keep observing for value change
它现在可以工作了,因为 self.x 实际上是对函数的调用,并且编译器足够礼貌地让函数真正完成它的工作。但是,我认为这是一种相当复杂的方式来做如此简单的事情。如果您面临“观察块内值的变化”的任务,您是否有任何其他方式对其进行编码,您将使用另一种模式?非常感谢!
【问题讨论】:
【参考方案1】:如果你使用一个变量并且不在循环中修改它,编译器优化会导致对变量的实际访问被优化掉,因为你的语句可以在编译时预先计算。
为了防止这种情况,您可以使用“volatile”关键字,它会阻止编译器应用这种类型的优化。
它确实适用于 getter 和 setter,因为您需要向您的实例发送一条消息,作为同步点。
【讨论】:
哇,volatile 关键字有效!我记得曾经在一本 C 书上读过它,但从未想过我会使用它!谢谢【参考方案2】:尝试如下声明 _x:
__block int _x;
通常也会复制块中使用的变量。这将向编译器表明,如果在块中修改了 _x,则更改应该在块之外可见。它可能会解决您的问题。
【讨论】:
__block
仅适用于局部变量以上是关于调试和发布配置之间的不同块行为的主要内容,如果未能解决你的问题,请参考以下文章