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

时间:2013-06-20 08:22:54

标签: iphone ios objective-c automatic-ref-counting block

The problematic application, simplified

我的程序运作完美。我向你保证我的生命,0个虫子。我自豪地尝试将应用程序打包为.ipa文件,以便使用TestFlight临时分发给我的beta测试人员。

该计划无效。应该发生的动画从未发生过。网络代码中断。美妙地淡出音乐的按钮根本没有做任何事情。

事实证明,罪魁祸首是新的闪亮的块。当我在模拟器或我的设备上测试我的程序时,我使用了默认的“Debug”构建配置。但是,当我将其存档以进行分发时(我相信稍后会提交到App Store),XCode使用另一个“Release”配置。进一步研究,不同之处在于优化级别(您可以在XCode的Build Settings中找到它):Debug使用None(-O0)但Release使用Fastest,Smallest(-Os)。我不知道,它是最快,最小,不起作用(tm)。是的,块在这两种配置之间表现不同。

所以,我开始着手解决问题。我已经将我即将改变世界的应用程序简化为简单的骨骼,如我在本文中所附的图像所示。视图控制器有一个实例变量x,初始值为0.如果我们按b,它将产生一个线程,它将连续检查x的值,当x变为1时改变底部标签。我们可以使用button更改x的值一个。

这是我的天真代码(我正在使用ARC btw):

@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”的指针访问它,而不是在本地副本上。它适用于调试配置!

但它不适用于Release版本。那么块可能毕竟是使用本地副本?好的,所以让我们明确地使用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实际上是对函数的调用,编译器很有礼貌,让函数实际完成它的工作。但是,我认为做一些如此简单的事情是一种相当复杂的方式。如果您面临“观察块内价值变化”的任务,是否还有其他方法可以编码它,您将使用另一种模式?非常感谢!

2 个答案:

答案 0 :(得分:1)

如果使用变量而不在循环中修改它,编译器优化可能会导致对变量的实际访问进行优化,因为您的语句可以在编译时预先计算。

为了防止这种情况,您可以使用“volatile”关键字,这会阻止编译器应用此类优化。

它适用于getter和setter,因为那时你需要向你的实例发送一条消息,作为同步点。

答案 1 :(得分:-1)

尝试按如下方式声明_x:

__block int _x;

通常会复制块中也使用的变量。这将向编译器指示如果在块中修改了_x,则更改应该在其外部可见。它可能会解决您的问题。