3

我有一个名为 logBu​​ffer 的 NSMutableArray 对象,它保存日志信息并每 N 行将其转储到一个文件中。发生这种情况时,我删除其所有条目,

[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-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的是 ,我们可以摆脱缓冲区副本并进行同步。

接下来,考虑评论和编辑的问题:

如果您只调用writeLogOnFilefrom addToLog,那么我会做以下两件事之一:

  1. writeLogOnFile将代码合并到addToLog中,因为无论如何它是一对一的。这确保没有任何东西会直接调用writeLogOnFile. 在这种情况下,完全包裹addToLog@synchronized(logBuffer) {}

  2. 如果您出于某种原因想保持writeLogOnFile分离,请将此方法设为类私有。在这种情况下,你可以摆脱 inside,因为理论上你知道你在课堂上做什么,但你也应该完全包裹@synchronized(logBuffer)在里面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 回答