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