#ios #objective-c #csv #memory-management #nsoutputstream
#iOS #objective-c #csv #управление памятью #nsoutputstream
Вопрос:
Я хочу свести к минимуму использование памяти при записи данных в файл CSV.
Для больших таблиц он использует больше памяти, даже если это временно.
Может кто-нибудь подсказать, как уменьшить использование памяти?
Возможно, я мог бы разделить действие для таблиц большего размера, записать больше файлов, а затем объединить их, но я еще не пробовал этого, возможно, я упускаю что-то очевидное.
Вот используемый в настоящее время код:
@autoreleasepool {
NSOutputStream *csvStream = [[NSOutputStream alloc] initToMemory];
[csvStream open];
CHCSVWriter *writer = [[CHCSVWriter alloc] initWithOutputStream:csvStream encoding:NSUTF8StringEncoding delimiter:';'];
NSArray *keySortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]];
if (writeHeader==YES) {
//> write header
NSMutableDictionary *firstRow = [[self sharedUploadManager].modifiedRows firstObject];
if (firstRow==nil) {
result = NO;
return resu<
}
NSArray *orderedKeys = [[firstRow allKeys] sortedArrayUsingDescriptors:keySortDescriptors];
for (NSString *columnName in orderedKeys) {
[writer writeField:columnName];
}
}
[writer finishLine];
@autoreleasepool {
//> write the rows
for (NSMutableDictionary *row in [self sharedUploadManager].modifiedRows) {
NSArray *orderedKeys = [[row allKeys] sortedArrayUsingDescriptors:keySortDescriptors];
for (NSString *key in orderedKeys ) {
NSString *field = [row objectForKey:key];
if ([field isKindOfClass:[NSNull class]]) {
[writer writeField:nil];
} else {
[writer writeField:field];
}
}
//> finish the line
[writer finishLine];
}
}
[writer closeStream];
NSData *buffer = [csvStream propertyForKey:NSStreamDataWrittenToMemoryStreamKey];
NSString *output = [[NSString alloc] initWithData:buffer encoding:NSUTF8StringEncoding];
if (![[NSFileManager defaultManager] fileExistsAtPath:csvPath]) {
[[NSFileManager defaultManager] createFileAtPath:csvPath contents:nil attributes:nil];
}
BOOL res = [[output dataUsingEncoding:NSUTF8StringEncoding] writeToFile:csvPath atomically:NO];
if (!res) {
NSLog(@"Error Creating CSV File path = %@", csvPath);
} else{
NSLog(@"Data saved! File path = %@", csvPath);
}
}
Я также пробовал эту логику раньше — немного чище, но с тем же результатом:
NSOutputStream *csvStream = [[NSOutputStream alloc] initToFileAtPath:csvPath append:YES];
[csvStream open];
CHCSVWriter *writer = [[CHCSVWriter alloc] initWithOutputStream:csvStream encoding:NSUTF8StringEncoding delimiter:';'];
if (writeHeader==YES) {
//> write header
NSMutableDictionary *firstRow = [rows firstObject];
if (firstRow==nil) {
result = NO;
return resu<
}
NSArray *orderedKeys = [[firstRow allKeys] sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]]];
for (NSString *columnName in orderedKeys) {
[writer writeField:columnName];
}
[writer finishLine];
}
//> write the rows
for (NSMutableDictionary *row in rows) {
NSArray *orderedKeys = [[row allKeys] sortedArrayUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"self" ascending:YES]]];
for (NSString *key in orderedKeys ) {
NSString *field = [row objectForKey:key];
if ([field isKindOfClass:[NSNull class]]) {
[writer writeField:nil];
}
else {
[writer writeField:field];
}
}
//> finish the line
[writer finishLine];
}
[writer closeStream];
Ответ №1:
Если вы не хотите использовать много памяти при создании большого файла CSV, не создавайте поток вывода на основе памяти. Создайте выходной поток в реальный файл. Тогда данные CSV будут записаны в файл, а не в память. Тогда файл может быть гигабайтным и использовать очень мало памяти.
Дополнительным преимуществом этого является отсутствие необходимости доступа к данным буфера, создания из них строки (теперь использование памяти удваивается), а затем записи строки в файл.
NSOutputStream *csvStream = [NSOutputStream outputStreamToFileAtPath:csvPath append:NO];
[csvStream open];
CHCSVWriter *writer = [[CHCSVWriter alloc] initWithOutputStream:csvStream encoding:NSUTF8StringEncoding delimiter:';'];
// write your CSV entries
[writer closeStream];
Вот и все. Для создания файла не требуется никакого другого кода.
В дополнение к этим изменениям вам необходимо изменить, где вы используете пул автоматического выпуска. Это должно быть внутри внешнего for
цикла.
//> write the rows
for (NSMutableDictionary *row in [self sharedUploadManager].modifiedRows) {
@autoreleasepool {
NSArray *orderedKeys = [[row allKeys] sortedArrayUsingDescriptors:keySortDescriptors];
for (NSString *key in orderedKeys ) {
NSString *field = [row objectForKey:key];
if ([field isKindOfClass:[NSNull class]]) {
[writer writeField:nil];
} else {
[writer writeField:field];
}
}
//> finish the line
[writer finishLine];
}
}
Это гарантирует, что память автоматически выпущенных объектов очищается после каждой строки.
Комментарии:
1. Здравствуйте, спасибо за ответ, но я на самом деле тоже это пробовал, все еще продолжает расти для файлов большего размера, этот опубликованный код был просто попыткой посмотреть, работает ли что-то по-другому… Я вижу, что распределение увеличивается во время сеанса отладки и с помощью инструмента распределения. При выделении кажется, что writeField потребляет много, поэтому при почти миллионе строк приложение вылетает. Сейчас это необычная ситуация для приложения, но однажды это может случиться, и я пытаюсь с этим справиться.
2. @maddy Я только что заметил, что в вашей логике не используется append, я попытаюсь посмотреть, что с этим произойдет.
3. Нет, все то же самое… Сами файлы не такие большие, кажется, есть утечка, но я не могу ее обнаружить… Самый большой файл составляет около 20 МБ.
4. Спасибо, я сделал это для обоих циклов for здесь (измененные объекты также могут быть большими), теперь это выглядит лучше, но объем памяти все еще увеличивается.