调试和发布配置之间的不同块行为

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 仅适用于局部变量

以上是关于调试和发布配置之间的不同块行为的主要内容,如果未能解决你的问题,请参考以下文章

在不同配置之间共享 .obj 文件

09-nginx常用配置详解

nginx 配置详解是啥?

为啥浮动计算和强制转换在调试和发布配置中显示不同的结果?

为啥发布和调试模式下的代码行为不同?

Windows Phone 中全局变量的调试和发布配置