2

私は過去 2 時間、この問題を解決しようとしてきましたが、あまり進展がありませんでした。

基本的に、同時に実行したい 3 つの NSOperations (実際には AFHTTPRequestOperations ですが、正確に理解すれば NSOperations として使用しても問題はありません) があり、それらすべてが完了したら、4 番目の操作を実行したいと考えています。プロセス中にエラーをチェックし、ユーザーに報告します (もちろん、NSOperations は NSOperationQueue 内に追加されます)。

最後の操作は、これら 3 つすべてに依存する NSBlockOperation であり、3 つの操作が完了したときにのみ正しく呼び出されます。このような構造になっています

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 によって @"" として報告されます。これは、4 つの NSOperations を実行する前に設定した値です。プロパティは、このように各操作の完了ブロック内で変更されます

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

割り当て後に_errorsプロパティをNSLoggingすると正しい値が表示され、NSOperationQueueを準備するマスターメソッドの後続の実行で値をチェックしても(@ ""に戻す前に)正しい値が読み取られますが、finishedUploadingだけが間違っています。

さらに、finishedUploading が正しい値を取得することがありますが、それは最初の NSLog の後でのみです (後続の if 条件が true と評価されます)。

完了ブロックの実行が早すぎて、実際には最後の NSOperation を実行する前に 1 秒の遅延を追加すると問題が解決するためだと思いましたが、無駄な遅延が追加されるため、最適な解決策ではありません (0.5 秒でも機能しません)。 . 解決策を探してみましたが、 @synchronized(_errors) を使用して _errors に (retain) を追加しても役に立ちません。

Apple のドキュメントでは、NSString はスレッド セーフであると主張しているため、何が間違っているのかわかりません。文字列に追加すると何らかの理由でこれが発生すると想定しましたが、文字列を直接設定しても問題が発生します。

編集

_errors = @"Error: Test error\n";文字列を追加すると問題が発生した場合に備えて、割り当てをに変更しました

また、NSLogs が順不同で到着していることにも気付きました (finishedUploading の NSLog は、_errors の値を変更するブロック内の NSLog の前に来ます)。

4

3 に答える 3

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 に答える