3

ログ情報を保持し、N 行ごとにファイルにダンプする logBu​​ffer という NSMutableArray オブジェクトがあります。その場合、すべてのエントリを削除します。

[logBuffer removeAllObjects]

時々これは例外をスローします:

[__NSArrayM removeObjectAtIndex:]: index 7 beyond bounds [0 .. 6]

removeAllObjects は配列内のすべてのオブジェクトを内部的に反復処理すると思いますが、どうすればその境界を超えることができるのかわかりません。私の唯一のことは、オブジェクトが削除されている間に配列を操作する別のスレッドがあるということですが、まったくわかりません。

何かご意見は?


編集:ここにいくつかの追加コードがあります:

- (void) addToLog:(NSString*)str {
    [logBuffer addObject:s];
    if ([logBuffer count] >= kBufferSize) {
        [self writeLogOnFile];
    }
}

- (void) writeLogOnFile {
    NSArray *bufferCopy = [NSArray arrayWithArray:logBuffer];   // create a clone, so that logBuffer doesn't change while dumping data and we have a conflict

    NSString *multiline = [bufferCopy componentsJoinedByString:@"\r\n"];
    multiline = [NSString stringWithFormat:@"%@\n", multiline];
    NSData *data = [multiline dataUsingEncoding:NSUTF8StringEncoding];
    NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
    [outputFileHandle seekToEndOfFile];
    [outputFileHandle writeData:data];
    [outputFileHandle closeFile];

    [logBuffer removeAllObjects];   // This is where the exception is thrown
}

[CrashManager addToLog:] は、常にメイン スレッド上ではなく、多数のクラスによって呼び出されます。


バックトレースは次のとおりです。

"0   AClockworkBrain             0x0008058f -[SWCrashManager backtrace] + 79",
"1   AClockworkBrain             0x0007fab6 uncaughtExceptionHandler + 310",
"2   CoreFoundation              0x041fe318 __handleUncaughtException + 728",
"3   libobjc.A.dylib             0x03c010b9 _ZL15_objc_terminatev + 86",
"4   libc++abi.dylib             0x044c9a65 _ZL19safe_handler_callerPFvvE + 13",
"5   libc++abi.dylib             0x044c9acd __cxa_bad_typeid + 0",
"6   libc++abi.dylib             0x044cabc2 _ZL23__gxx_exception_cleanup19_Unwind_Reason_CodeP17_Unwind_Exception + 0",
"7   libobjc.A.dylib             0x03c00f89 _ZL26_objc_exception_destructorPv + 0",
"8   CoreFoundation              0x041171c4 -[__NSArrayM removeObjectAtIndex:] + 212",
"9   CoreFoundation              0x04153f70 -[NSMutableArray removeAllObjects] + 96",
"10  AClockworkBrain             0x000817c3 -[SWCrashManager writeLogOnFile] + 691",
"11  AClockworkBrain             0x0008141d -[SWCrashManager addToLog:] + 429",
*** Terminating app due to uncaught exception 'NSRangeException', reason: '*** -[__NSArrayM removeObjectAtIndex:]: index 7 beyond bounds [0 .. 6]'

編集#2

に関する提案を読んだ後@synchronize、次のように変更しました。

- (void) addToLog:(NSString*)str {
    [self performSelectorOnMainThread:@selector(doAddToLog:) withObject:str waitUntilDone:YES];
}

- (void) doAddToLog:(NSString*)str {
    // Do the real stuff
}

- (void) writeLogOnFile {
    [self performSelectorOnMainThread:@selector(doWriteLogOnFile) withObject:nil waitUntilDone:YES];
}

- (void) doWriteLogOnFile {
    // Do the real stuff
}

コードを数時間テストしましたが、例外はスローされませんでした。以前は 1 時間に 1 ~ 2 回クラッシュしていたので、問題は修正されたと思います。@synchronizeこのアプローチが提案とどのように異なるかを誰かが説明できますか?

また、waitUntilDone:YES を使用するのが賢明ですか、それともこの場合は NO の方がよいでしょうか?

4

2 に答える 2

4

使用@synchronized(logBuffer):

- (void) addToLog:(NSString*)str {
    @synchronized(logBuffer) {
      [logBuffer addObject:s];
    }
    if ([logBuffer count] >= kBufferSize) {
        [self writeLogOnFile];
    }
}

- (void) writeLogOnFile {
    @synchronized(logBuffer) {    
      NSString *multiline = [logBuffer componentsJoinedByString:@"\r\n"];
      multiline = [NSString stringWithFormat:@"%@\n", multiline];
      NSData *data = [multiline dataUsingEncoding:NSUTF8StringEncoding];
      NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
      [outputFileHandle seekToEndOfFile];
      [outputFileHandle writeData:data];
      [outputFileHandle closeFile];

     [logBuffer removeAllObjects];   // This is where the exception is thrown
    }
}

編集: を使用しているので@synchronized、バッファ コピーを削除して同期するだけです。

続けて、コメントと編集された質問を考慮して:

writeLogOnFileからのみ呼び出す場合はaddToLog、次の 2 つのいずれかを行います。

  1. いずれにしても 1 対 1 であるため、writeLogOnFileコードを にマージします。addToLogこれにより、 が直接呼び出されることはありませんwriteLogOnFile。この場合addToLog@synchronized(logBuffer) {}

  2. writeLogOnFileなんらかの理由で分離したい場合は、このメソッドをクラスに対してプライベートにします。この場合、理論的にはクラス内で何をしているかを知っているため、@synchronized(logBuffer)withinを取り除くことができますが、完全にクラス内でラップする必要もあります。writeLogOnFileaddToLog@synchronized(logBuffer) {}

ご覧のとおり、どちらの場合も、絶対にaddToLog完全にシングル スレッド化する必要があります@synchronized(または元の回答を維持する必要があります)。非常にシンプルで、コードをクリーンに保ち、編集した質問が回避しようとしているすべてのスレッドの問題を取り除きます。この@synchronizedパターンは、問題を解決するために記述したすべてのラッパー コードを記述することを避けるために特別に作成されました。つまり、すべてをメイン スレッド (または特定のスレッド) に強制的に実行させます。

完全を期すために、ここに私が書く完全なコードを示します。

- (void) addToLog:(NSString*)str {
    @synchronized(logBuffer) {
      [logBuffer addObject:s];
      if ([logBuffer count] >= kBufferSize) {  // write log to file
        NSString *multiline = [logBuffer componentsJoinedByString:@"\r\n"];
        multiline = [NSString stringWithFormat:@"%@\n", multiline];
        NSData *data = [multiline dataUsingEncoding:NSUTF8StringEncoding];
        NSFileHandle *outputFileHandle = [NSFileHandle fileHandleForWritingAtPath:logFilePath];
        [outputFileHandle seekToEndOfFile];
        [outputFileHandle writeData:data];
        [outputFileHandle closeFile];
        [logBuffer removeAllObjects];
      }
    }
}
于 2013-04-02T16:11:44.913 に答える
2

あなたが提供した情報では、何が問題なのかを判断するのは困難ですが、私によると、問題は、メインスレッドまたは他のスレッドを使用して反復処理中に配列を変更している可能性があることです..

于 2013-04-02T14:48:10.893 に答える