2013-04-02 3 views
3

У меня есть объект NSMutableArray, называемый logBuffer, который содержит информацию журнала и выгружает его в файле каждые N строк. Когда это произойдет, я удалить все свои записи, сNSMutableArray removeAllObjects за пределами исключения исключений

[logBuffer removeAllObjects] 

Иногда это бросает исключение:

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

Я думаю removeAllObjects внутренне перебирает через все объекты в массиве, но я не уверен, как это может выйти за ее пределы. Мое единственное, хотя в том, что есть еще один поток, который манипулирует массивом, когда объекты удаляются, но я совсем не уверен.

Любые мысли?


EDIT: Вот некоторые дополнительный код:

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

EDIT # 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 
} 

Я тестировал код для fe w часов, и он не выбрал исключение. Раньше он падал примерно 1-2 раза в час, поэтому я предполагаю, что проблема исправлена. Может кто-нибудь объяснить, как этот подход отличается от предложения @synchronize?

Кроме того, разумно ли использовать waitUntilDone: ДА или, возможно, НЕТ было бы лучше в этом случае?

+2

«Моя единственная, хотя в том, что есть еще одна нить, которая манипулирует массив в то время как объекты удаляются» - состояние гонки была моя мгновенная подумал. Как вы используете массив? – 2013-04-02 14:45:21

+0

Попробуйте выполнить регистрацию размера массива прямо перед вызовом * removeAllObjects *. Затем в журнале сбоев прочитайте размер массива (в вашем примере, [0..6] дает размер 7). Если размер изменяется, вы, вероятно, редактируете свой массив во время выполнения * removeAllObjects *. – rdurand

+0

Вы можете попробовать итерировать и удалить объекты вручную, возможно, вы найдете что-то. – rdurand

ответ

2

Хотя информация, предоставленная это трудно сказать, что происходит неправильно, но по мне эта проблема может быть, что вы могли бы модифицировать массив в то время как итерация через него, используя основной поток или другой поток ..

+0

Один из способов это может произойти, если метод dealloc элемента массива модифицирует массив –

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 
    } 
} 

Edit: Так как я использую @synchronized, мы можем избавиться от буфера копирования и просто синхронизировать.

В продолжении рассмотрения замечаний и отредактированный вопрос:

Если вы только позвонить writeLogOnFile из addToLog, то я бы это одна из двух вещей:

  1. Объединить writeLogOnFile код в addToLog, так это все равно 1-к-1. Это гарантирует, что ничто никогда не вызовет прямо writeLogOnFile.В этом случае, оберните addToLog полностью в @synchronized(logBuffer) {}

  2. Если вы хотите сохранить writeLogOnFile отдельно по какой-либо причине, то сделать этот метод частного к классу. В этом случае, вы можете избавиться от @synchronized(logBuffer) в writeLogOnFile, так как в теории, вы знаете, что вы делаете в классе, но вы должны также обернуть addToLog полностью в @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]; 
     } 
    } 
} 
+0

Я бы вместо этого выполнял '@synchronized (logBuffer) {NSArray * bufferCopy = ...; [logBuffer removeAllObjects]; } 'в начале' writeLogOnFile'. Это защищает от возможности добавления элемента журнала между дублированием массива и удалением объектов - любые добавленные объекты не будут записываться в файл, но будут удалены только после удаления 'logBuffer'. – bdesham

+0

На самом деле второй @syncronized не нужен, если единственный способ ввода записи ... через (синхронизированный) addToLog. Но это не так, как я бы это сделал, я не думаю. –

+0

Мой ответ - действительно образец того, что можно сделать. Я предполагаю, что @dimitrios, вероятно, вызывает '[logBuffer addObject: s]' из многих мест и аналогично '[self writeLogOnFile]'. Использование '@ synchronized' позволяет очень гибко описывать, как и когда это можно назвать. – Rikkles

Смежные вопросы