简体   繁体   English

NSMutableArray removeAllObjects超出边界异常

[英]NSMutableArray removeAllObjects beyond bounds exception

I have an NSMutableArray object called logBuffer, which holds log information and dumps it in a file every N lines. 我有一个名为logBu​​ffer的NSMutableArray对象,它保存日志信息并将其每N行转储到一个文件中。 When that happens, I remove all its entries, with 当发生这种情况时,我会删除所有条目

[logBuffer removeAllObjects]

Sometimes this throws an exception: 有时会引发异常:

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

I guess removeAllObjects internally iterates through all objects in the array, but I am not sure how it can go beyond its bounds. 我想removeAllObjects在内部迭代遍历数组中的所有对象,但我不确定它是如何超越它的界限的。 My only though is that there is another thread that manipulates the array while the objects are being removed, but I am not sure at all. 我唯一的问题是,在移除对象时还有另一个操作数组的线程,但我根本不确定。

Any thoughts? 有什么想法吗?


EDIT: Here's some additional code: 编辑:这是一些额外的代码:

- (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:] is called by dozens of classes, not always on the main thread. [CrashManager addToLog:]由几十个类调用,并不总是在主线程上。


Here's the backtrace: 这是回溯:

"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]'

EDIT #2 编辑#2

After reading the suggestion about @synchronize , I modified it to: 在阅读有关@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
}

I tested the code for a few hours and it hasn't thrown an exception. 我测试了几个小时的代码,并没有抛出异常。 It used to crash about 1-2 times per hour, so I assume that the issue is fixed. 它曾经每小时崩溃大约1-2次,所以我认为问题是固定的。 Can someone explain how this approach differs from the @synchronize suggestion? 有人可以解释这种方法与@synchronize建议的区别吗?

Also, is it wise to use waitUntilDone:YES or perhaps NO would be better in this case? 另外,使用waitUntilDone是明智的:YES或者在这种情况下NO会更好吗?

Use @synchronized(logBuffer) : 使用@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
    }
}

Edit: Since I'm using @synchronized , we can get rid of the buffer copy and just synch. 编辑:因为我正在使用@synchronized ,我们可以摆脱缓冲区副本,只需同步。

In continuation, considering the comments and edited question: 继续,考虑评论和编辑问题:

If you only call writeLogOnFile from addToLog , then I would do one of two things: 如果你只从addToLog调用writeLogOnFile ,那么我会做两件事之一:

  1. Merge the writeLogOnFile code into addToLog , since it's 1-to-1 anyway. writeLogOnFile代码合并到addToLog ,因为它无论如何都是1比1。 This ensures that nothing will ever directly call writeLogOnFile . 这确保了什么都不会直接调用writeLogOnFile In this case, wrap addToLog completely within @synchronized(logBuffer) {} 在这种情况下,将addToLog完全包装在addToLog @synchronized(logBuffer) {}

  2. If you want to keep writeLogOnFile separate for whatever reason, then make this method private to the class. 如果由于某种原因想要将writeLogOnFile分开,那么将此方法设为私有。 In this case, you can get rid of @synchronized(logBuffer) within writeLogOnFile since in theory you know what you are doing within the class, but you should also wrap addToLog completely within @synchronized(logBuffer) {} 在这种情况下,你可以在writeLogOnFile @synchronized(logBuffer) ,因为理论上你知道你在类中做了什么,但是你也应该将addToLog完全包装在addToLog @synchronized(logBuffer) {}

As you can see, in both cases you should absolutely make addToLog completely single-threaded through @synchronized (or keep the original answer). 正如你所看到的,在这两种情况下,你绝对应该做addToLog完全单线程通过@synchronized (或保留原来的答案)。 It's very simple, keeps your code clean, and gets rid of all the threading issues that your edited question is trying to work around. 它非常简单,可以保持代码清洁,并消除您编辑的问题试图解决的所有线程问题。 The @synchronized pattern was created specifically to avoid writing all the wrapper code that you wrote to solve your problem, namely forcing everything through the main thread (or a specific thread). @synchronized模式是专门创建的,以避免编写您编写的所有包装代码来解决您的问题,即通过主线程(或特定线程)强制一切。

For completeness, here's the complete code that I would write: 为了完整起见,这里是我要写的完整代码:

- (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];
      }
    }
}

虽然您提供的信息很难说出现了什么问题但是根据我的问题可能是您可能正在使用主线程或其他线程迭代它时修改数组。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM