2

在过去的两个小时里,我一直在尝试解决这个问题,但没有取得太大进展,我想也许是时候尝试在这个问题上寻求一些外部帮助了。

基本上,我想要同时运行三个 NSOperations(准确地说它们实际上是 AFHTTPRequestOperations,但如果我理解正确,将它们用作 NSOperations 没有问题),当它们全部完成时,我想运行第四个操作在此过程中检查错误并将其报告给用户(NSOperations 当然是添加到 NSOperationQueue 中)。

最后一个操作是一个 NSBlockOperation ,它依赖于这三个操作,只有三个操作完成后才能正确调用。它的结构是这样的

NSOperation *finishedUploading = [NSBlockOperation blockOperationWithBlock:^{
 NSLog(@"Completed. Errors: %@", _errors);
 _textArea.editable = YES;
 if (![_errors isEqualToString: @""])
 {
  //Trim the last newline
  _errors = [_errors substringToIndex: _errors.length-1];
  dispatch_async(dispatch_get_main_queue(), ^{
   //Show error message
  });
 } else {
  dispatch_async(dispatch_get_main_queue(), ^{
   //Show success dialog
  });
 }
}];

但是,我试图访问的 _errors 属性是这样声明的

@property NSString *errors;

在finishedUploading 块中被错误地读取。它的值被 NSLog 报告为@"",这是我在执行四个 NSOperation 之前设置的值。属性在每个操作的完成块内更改,如下所示

_errors = [_errors stringByAppendingString: @"Error: Test error\n"];

NSLogging _errors 属性在分配后将显示正确的值,甚至在准备 NSOperationQueue 的主方法的后续执行中检查该值(在将其设置回@“”之前)读取正确的值,只有 finishedUploading 出错了。

此外,有时finishedUploading 确实获得了正确的值,但仅在第一个NSLog 之后(随后的if 条件被评估为真)。

我认为这是因为完成块执行得太早,实际上在执行最后一个 NSOperation 之前添加一秒延迟确实可以解决问题,但这不是最佳解决方案,因为它增加了无用的延迟(半秒甚至不起作用) . 我曾尝试四处寻找解决方案,但即使使用 @synchronized(_errors) 并将(保留)添加到 _errors 也无济于事。

苹果的文档坚持认为 NSString 是线程安全的,所以我不确定我做错了什么,我认为附加到字符串以某种方式导致了这种情况,但即使直接设置字符串也会导致问题。

编辑

我已将分配更改为_errors = @"Error: Test error\n";以防万一附加字符串导致问题

我还注意到 NSLogs 乱序到达(finishedUploading 的 NSLog 位于更改 _errors 值的块内的 NSLog 之前)

4

3 回答 3

0

块中的变量是按值传递的,因此块中的错误变量只是错误属性的副本,因此您可以在未修改原始属性的块中看到正确的 NSLog。

您应该做的是使用 __block 前缀将对变量的引用传递给块。我不是块向导,但也许这就是原因

于 2013-11-05T18:15:47.413 回答
0

[_errors stringByAppendingString: @"Error: Test error\n"];创建一个新NSString实例,而不是修改块中捕获的实例。使用NSMutableStringappendString修改它,然后您将在块内获得正确的值。

于 2013-11-05T18:38:03.047 回答
0

块文字制作捕获的本地状态的 const 副本。该_errors变量是一个局部引用——它_errors与封闭范围内的实例变量不同。块内的引用值是在_errors定义块时初始化的,而不是在执行块时。这就是为什么您没有看到更新的值。

通常,从块中引用实例变量往往会令人困惑。如果它以您期望的方式工作,那在概念上将代表对封装的重大违反,所以最好不要这样做。

在任何情况下,请考虑使用块内的访问器方法来访问封闭范围内对象的状态,而不是通过直接引用对象的实例变量来获取它们的快照。更一般地说,当使用声明的属性时,更喜欢使用属性的访问器而不是直接获取和设置底层实例变量;这可以帮助您避免像这样的小陷阱。

您重写的块可能如下所示:

NSOperation *finishedUploading = [NSBlockOperation blockOperationWithBlock:^{
    NSLog(@"Completed. Errors: %@", self.errors);
    self.textArea.editable = YES;
    if (![self.errors isEqualToString:@""])
    {
         //Trim the last newline
         self.errors = [self.errors substringToIndex:self.errors.length-1];
         dispatch_async(dispatch_get_main_queue(), ^{
             //Show error message
         });
     } else {
         dispatch_async(dispatch_get_main_queue(), ^{
             //Show success dialog
         });
     }
}];

请注意,该块正在捕获self(当它直接访问 ivar 时也是如此),因此您可能正在创建一个保留周期。为避免这种情况,请参阅有关破坏块保留周期的 SO 问题,例如这个:块中的弱引用和保留周期

于 2013-11-05T20:34:01.920 回答