Ios NSMutableArray RemoveAllObject超出边界异常
我有一个名为logBuffer的NSMutableArray对象,它保存日志信息并每隔N行将其转储到一个文件中。当这种情况发生时,我将删除它的所有条目,并使用Ios NSMutableArray RemoveAllObject超出边界异常,ios,objective-c,exception,nsmutablearray,Ios,Objective C,Exception,Nsmutablearray,我有一个名为logBuffer的NSMutableArray对象,它保存日志信息并每隔N行将其转储到一个文件中。当这种情况发生时,我将删除它的所有条目,并使用 [logBuffer removeAllObjects] 有时这会引发异常: [__NSArrayM removeObjectAtIndex:]: index 7 beyond bounds [0 .. 6] 我猜removeAllObjects在内部会遍历数组中的所有对象,但我不确定它如何超越其边界。我唯一的想法是,在移除对象时,有
[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是否明智:在这种情况下,是或否会更好?虽然您提供的信息很难判断出哪里出了问题,但根据我的说法,问题可能是您可能在使用主线程或其他线程迭代数组时修改了数组。使用
@synchronized(日志缓冲区)
:
编辑:因为我使用的是@synchronized
,所以我们可以去掉缓冲区副本,只需同步
继续,考虑到评论和编辑的问题:
如果您仅从addToLog
调用writeLogOnFile
,那么我将执行以下两种操作之一:
writeLogOnFile
代码合并到addToLog
,因为它是1对1。这确保没有任何东西会直接调用writeLogOnFile
。在这种情况下,将addToLog
完全包装在@synchronized(logBuffer){}
writeLogOnFile
分开,则将此方法设为类的私有方法。在这种情况下,可以去掉@synchronized(logBuffer)
在writeLogOnFile
中,因为理论上你知道你在类中做什么,但是你也应该将addToLog
完全包装在@synchronized(logBuffer){}
@synchronized
(或保留原始答案)使addToLog
完全单线程化。它非常简单,可以保持代码干净,并消除您编辑的问题试图解决的所有线程问题。@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];
}
}
}
“我唯一的想法是,当对象被移除时,有另一个线程操纵数组”-竞争条件也是我立即想到的。您如何使用数组?在调用removeAllObjects之前,尝试记录数组的大小。然后在崩溃日志中,读取数组的大小(在您的示例中,[0..6]给出了一个7)的大小。如果大小更改,您可能正在执行removeAllObjects期间编辑数组。您可以尝试手动迭代和删除对象,也许您会发现一些问题。请记住,如果由于存储错误而进行此崩溃日志记录,堆可能已损坏,并且所有赌注都已取消。如果从多线程时,您有几个严重的争用情况。至少您不应该重用阵列,而是每次创建一个新的阵列(除了一些同步)因此,如果在写入过程中发生新的日志操作,这两个操作将不会发生干扰。可能发生这种情况的一种方法是,如果数组成员的dealloc方法修改了arrayI,则改为执行
@synchronized(logBuffer){NSArray*bufferCopy=…;[logBuffer removeAllObjects];}
在writeLogOnFile
的开始处。这可以防止在复制数组和删除对象之间添加日志项的可能性——任何添加的对象都不会写入文件,而是在清除logBuffer
时删除。实际上,如果打开,则不需要第二个@syncronized输入write…的唯一方法是通过(同步的)addToLog。但我认为这不是我的方法。我的答案实际上是一个可以完成的示例。我假设@dimitrios可能从许多地方调用[logBuffer addObject:s]
,类似的还有[self writegonfile]
。使用@synchronized
可以非常灵活地选择如何和何时调用此功能。@Rikkles感谢您的建议。我正在呼叫[CrashManager addToLog:]来自许多地方,但除了addToLog:和writeLogOnFile方法内部之外,没有直接调用logBuffer。我再次编辑了我的原始问题,我想了解您的建议是否比我实现的建议更可取。
- (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];
}
}
}