Async читает большие файлы результатов в блоках нулевых чтений на iOS

Это меня насторожило. Я пытаюсь прочитать файл CSV размером 6 МБ в iOS по строкам. Я попытался использовать простые указатели файлов C и опрос NSInputStream, но остановился на следующем, который считал самым чистым. Все три подхода приводят к тому, что кажется случайным блоком чтения, возвращающим успех, но заполняют буфер нулевыми байтами. Я говорю "случайный", но имеет последовательность. Чтения перестают работать в одной и той же точке при повторном запуске программы, а число чтений подозрительно (подробнее об этом ниже).

- (id)initWithFileAtPath:(NSString *)path {
 if ((self = [super init])) {
 filePath = [path copy];
 queue = [[NSOperationQueue alloc] init];
 queue.maxConcurrentOperationCount = 1;
 buffer = [[NSMutableString alloc] init];
 bytes = malloc(CHUNK_SIZE * sizeof(UTF8Char));
 }

 return self;
}

- (void)dealloc {
 [filePath release];
 [queue release];
 [buffer release];
 free(bytes);
 [super dealloc];
}

- (void)stream:(NSInputStream *)stream handleEvent:(NSStreamEvent)eventCode {
 switch (eventCode) {
 case NSStreamEventOpenCompleted:
 break;
 case NSStreamEventHasBytesAvailable:
 [queue addOperationWithBlock:^{
 [self readChunk: stream];
 [self drainBuffer];
 }];
 break;
 case NSStreamEventEndEncountered:
 if ([buffer length] > 0) {
 [delegate reader:self didReadLine:[NSString stringWithString:buffer]];
 [buffer setString:@""];
 }

 [stream close];
 [stream removeFromRunLoop:[NSRunLoop currentRunLoop]
 forMode:NSDefaultRunLoopMode];

 [stream release];

 [delegate readerDidFinishReading:self];

 break;
 default:
 NSLog(@"StreamReader: event %d", eventCode);
 break;
 }
}

- (void)enumerateLines {
 NSInputStream *stream = [[NSInputStream alloc] initWithFileAtPath:filePath];
 stream.delegate = self;

 [stream scheduleInRunLoop:[NSRunLoop currentRunLoop]
 forMode:NSDefaultRunLoopMode];

 [stream open];
}

- (void)readChunk: (NSInputStream*)stream {
 NSInteger readSize = [stream read:bytes maxLength:CHUNK_SIZE];
 if (readSize) {
 if (bytes[0] == '\0') {
 NSLog(@"null buffer %d", readSize);
 }
 NSString *string = [[NSString alloc] initWithBytes:bytes
 length:readSize
 encoding:NSUTF8StringEncoding];
 [buffer appendString:string];
 [string release];
 } else {
 NSLog(@"StreamReader: read zero bytes");
 }
}

- (void)drainBuffer {
 static NSCharacterSet *newlines = nil;
 if (newlines == nil) {
 newlines = [NSCharacterSet newlineCharacterSet];
 }

 NSRange newlinePos;
 while ((newlinePos = [buffer rangeOfCharacterFromSet:newlines]).location != NSNotFound) {
 NSString *line = [buffer substringToIndex:newlinePos.location];

 // remove the line from the buffer along with line separator
 [buffer deleteCharactersInRange: (NSRange){0, [line length]}];
 while ([buffer length] > 0 && [newlines characterIsMember:[buffer characterAtIndex:0]]) {
 [buffer deleteCharactersInRange:(NSRange){0, 1}];
 }

 [delegate reader:self didReadLine: line];
 }
}

При чтении файла 6 МБ дважды я получаю серию из 96 "плохих чтений", когда CHUNK_SIZE равно 1024. Если CHUNK_SIZE равно 512, будет серия 192 "плохих чтений". Что я подразумеваю под "плохим чтением"? Сообщение чтения NSInputStream возвращает успех, и в обратном вызове делегата не возникает событие ошибки. Однако буфер bytes имеет все нулевые значения.

  • iOS 7.0.4, iPad 2
  • НЕ происходит на рабочем столе
  • НЕ происходит в симуляторе
  • уменьшая размер файла до aprox. 1MB "исправляет" проблему на iPad

Скорее всего, стоит отметить, что я создаю класс читателя в основном потоке пользовательского интерфейса.

Итак... я делаю что-то тонко (или не тонко) здесь неправильно? Или я обнаружил какую-то неясную ошибку iOS?

1 ответ

По крайней мере, одна проблема заключается в том, что вы читаете случайные куски потока UTF8, а затем предполагаете, что то, что вы получаете, является последовательным. Если вы получите кусок строки, который "ломается" в середине кодировки UTF8, это вызовет множество проблем. Если вы хотите выполнить частичное строение, ваш алгоритм будет нуждаться в переделке, чтобы этого не произошло - это не тривиальная вещь.

licensed under cc by-sa 3.0 with attribution.