0

有问题的应用程序,简化

我的程序完美运行。我用我的生命向你保证,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 实际上是对函数的调用,并且编译器足够礼貌地让函数真正完成它的工作。但是,我认为这是一种相当复杂的方式来做如此简单的事情。如果您面临“观察块内值的变化”的任务,您是否有任何其他方式对其进行编码,您将使用另一种模式?非常感谢!

4

2 回答 2

1

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

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

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

于 2013-06-20T08:37:41.937 回答
-1

尝试如下声明 _x:

__block int _x;

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

于 2013-06-20T09:37:23.180 回答