【问题标题】:Accessing properties on NSOperation访问 NSOperation 上的属性
【发布时间】:2013-11-05 18:03:01
【问题描述】:

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

基本上,我想同时运行三个 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 也无济于事。

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

编辑

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

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

【问题讨论】:

    标签: ios objective-c objective-c-blocks nsoperation nsoperationqueue


    【解决方案1】:

    块文字制作捕获的本地状态的 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 问题,例如:Weak references in blocks and retain cycles

    【讨论】:

    • 我尝试过使用 self.errors 但它没有帮助,但似乎这整个混乱是由 NSLog 订单证明的finishedUploading操作提前触发引起的,所以我最终放弃了依赖关系并在每次操作结束后手动检查状态。尽管如此,我还是会考虑这个保留周期问题,因为这听起来像是将来会再次困扰我的事情。
    【解决方案2】:

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

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

    【讨论】:

    • 您的意思是将我的属性声明更改为“@property __block NSString *errors;”?我试过了(我又试了一次),但同样不可靠
    • 如果你需要传递一个指针,使用这个:__block NSString *blockErrors = _errors;,然后在一个块中使用blockErrors变量。
    • 还是没有运气。我注意到finishedUploading NSLog 出现在关于设置_errors 属性的NSLog 之前。我不知道它是否准确,但似乎finishedUploading 启动得太早了,尽管它依赖于其他操作。
    【解决方案3】:

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

    【讨论】:

    • 我之前尝试过,但不幸的是它没有任何改变。我目前也以这种方式分配属性_errors = @"Error: Test error\n";,而不是附加字符串,但仍然没有。
    • 看看这个:NSMutableString *str = [NSMutableString stringWithString:@""]; void(^b1)(void) = ^{ [str appendString:@"1"]; }; void(^b2)(void) = ^{ [str appendString:@"2"]; }; b1(); b2(); NSLog(@"%@",str);
    • 同时打印所有块中的错误地址,以检查您是否正在修改/检查同一个对象并让我知道。 NSLog(@"_errors = %p", _errors);
    • 您的示例运行良好,但不幸的是切换到 NSMutableString 并没有帮助。在这一点上,我确信字符串工作得很好,但由于某种原因,finishedUploading 过早触发:/
    • 我在完成时确实有 NSLog,并且 finishedUploading 的 NSLog 几乎总是出现在单个块完成之前。我不知道为什么。尽管使用了 __block 技巧,但在输出 _errors 的地址时,我也会得到各种不同的地址。我将尝试再次将其设为 NSMutableString,看看是否有帮助
    猜你喜欢
    • 2013-04-17
    • 1970-01-01
    • 2014-12-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-07-05
    • 1970-01-01
    • 2015-11-03
    相关资源
    最近更新 更多