Преобразование / загрузка больших объемов данных с iPad на Dropbox
Я заканчиваю свое приложение, пропуская его через инструменты, а также подчеркивая его большими объемами данных. Тесты инструментов проходят хорошо, но стресс-тест - это то, где у меня возникают проблемы. Не вдаваясь в подробности, я даю своему приложению все больше и больше Core Data
события, с которыми он должен экстраполировать данные, строить графики и представлять местоположения на MKMapView
пример. Я начал с малого и увеличил до 56000 событий, которые он обрабатывал без каких-либо утечек или предупреждений памяти (и я был очень горд этим за все это).
В моем приложении реализован API-интерфейс Dropbox, позволяющий загружать и скачивать шаблоны и данные для синхронизации. Файлы, загруженные из моего приложения, конвертируются из Core Data
для NSDictionary
затем NSData
, Я создаю временную папку для данных, затем загружаю этот файл в Dropbox, который работает нормально..... нормально. Если я пытаюсь загрузить свой файл данных с 56000 событиями, то происходит сбой. Я зарегистрировал это и наблюдал, как данные преобразованы. Он достигает последнего события без проблем, но когда он должен начать загрузку в Dropbox, приложение вылетает, и я не могу понять, почему. Я вижу предупреждения памяти всплывающие на моем журнале. Как правило, это будет уровень =1, уровень =2, уровень =1, уровень =2, затем падение, что смущает меня, так как никогда не достигает уровня =3.
Большая часть информации, которую я нашел, находится в моей редакции на дне. Ниже приведен соответствующий код:
- (void)uploadSurveys:(NSDictionary *)dict {
NSArray *templateArray = [dict objectForKey:@"templates"];
NSArray *dataArray = [dict objectForKey:@"data"];
NSString *filename;
NSLog(@"upload called");
if ([templateArray count] || [dataArray count]) {
if ([templateArray count]) {
// irrelevent code;
}
if ([dataArray count]) {
SurveyData *survey;
for (int i = 0; i < [dataArray count]; i++) {
BOOL matchExists = NO;
// ...... code to make sure no file exists in dropbox folder and creates new version if necessary;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *data = [self convertSurvey:survey];
dispatch_async(dispatch_get_main_queue(), ^{
[self uploadData:data withFilename:filename];
NSLog(@"converted and uploading");
});
});
}
}
}
[self convertSurvey:survey]
просто преобразует мой Core Data
Возражать NSData
,
- (void)uploadData:(NSData *)data withFilename:(NSString *)filename {
NSFileManager *manager = [NSFileManager defaultManager];
NSString *pathComponent = [NSString stringWithFormat:@"tempData.%@", filename];
NSString *path = [NSTemporaryDirectory() stringByAppendingPathComponent:pathComponent];
if ([manager createFileAtPath:path contents:data attributes:nil]) {
[self.restClient uploadFile:filename toPath:[NSString stringWithFormat:@"/%@", currentSearch] fromPath:path];
NSLog(@"uploading data");
}
}
Любая помощь будет очень оценена, и я заранее благодарю вас. Я просто пытаюсь выяснить, не использую ли я неправильный подход для больших файлов, или это просто запрещено. Если мне нужно разделить файлы, это нормально, но я бы предпочел узнать, что происходит, что мешает моему приложению выполнить это действие, прежде чем пытаться обойти это. Еще раз спасибо
ОБНОВЛЕНИЕ: Поскольку эта проблема теперь является единственным препятствием для выпуска моего приложения, я добавляю вознаграждение к этому вопросу, чтобы надеяться получить решение или обходной путь. Это будет в течение недели, после чего, скорее всего, через некоторое время я собираюсь просто разделить файлы при их загрузке, чтобы гарантировать, что этот видимый предел размера не будет достигнут. Этот подход не идеален, поэтому лучшее решение приветствуется, но это мой план резервного копирования, если он не может принести что-то более удобное.
РЕДАКТИРОВАТЬ: кажется, что NSTemporaryDirectory
не играет никакой роли в этом вообще. Вот новая ситуация. Как вы можете видеть в коде выше, NSData *data = [self convertSurvey:survey];
вызывается во вторичном потоке (что не является проблемой). Я регистрировал созданные объекты и знал, что они достигли последнего, но никогда не думал проверить и посмотреть, NSData
файл был возвращен. Оказывается, это не так. Короче говоря, я преобразую все свои объекты базовых данных в массивы и помещаю их в словарь (только для соответствующего опроса / данных, которые необходимо преобразовать). Это действительно работает, и словарь создан. Затем я создаю NSData
использование файла NSData *data = [NSKeyedArchiver archivedDataWithRootObject:d];
где d
мой словарь Сразу после этого я звоню return data;
установить значение для NSData *data = [self convertSurvey:survey];
, В таком случае, кажется, NSData
или же NSKeyedArchiver
здесь виноваты Согласно документации Apple:
При использовании 32-битного Какао размер данных зависит от теоретического ограничения в 2 ГБ (на практике, поскольку память будет использоваться другими объектами, этот предел будет меньше); при использовании 64-битного Какао размер данных ограничивается теоретическим пределом около 8EB (на практике этот предел не должен учитываться).
Я проверил размеры файлов маленькими приращениями, чтобы увидеть, где происходит сбой. Я успешно получил 48,2 МБ данных, но не 51,5 МБ, что заставляет меня полагать, что проблема возникает примерно в 50 МБ, что намного ниже теоретического предела для NSData
(если нет расхождений между iOS и OS X в этом отношении).
Надеемся, что эта новая информация поможет решить эту проблему
2 ответа
Ограничение в 2 ГБ для NSData является абсолютно теоретическим для iOS, даже iPhone 4 имеет только 512 МБ ОЗУ, и iOS (в отличие от Mac OS X) не может поменяться, поэтому, если ваша физическая ОЗУ заполнена, вы зависаете (или ваше приложение завершается раньше). тот).
50 МБ NSData
один объект уже очень большой, и это не единственный объект, который у вас есть в памяти - учитывая, что вы преобразуете данные из базовых данных в представление словаря, а затем NSData
Вы, вероятно, потребляете как минимум вдвое больше памяти (вероятно, больше). Системе и другим приложениям также требуется оперативная память, поэтому вы, вероятно, достигли предела.
Попробуйте запустить свое приложение в Instruments, чтобы увидеть, сколько памяти вы фактически используете.
Чтобы уменьшить пиковое использование памяти, у вас есть несколько вариантов, которые в значительной степени зависят от вашей модели данных:
Как предложил Джейсон Форман в своем ответе, постарайтесь не хранить весь файл в памяти сразу. С помощью
NSFileHandle
Вы можете записывать порции данных в файл без необходимости хранить все данные в памяти сразу. Конечно, для этого необходимо соответствующим образом подготовить данные, чтобы их можно было разбить на куски. Подход более высокого уровня может заключаться в сериализации ваших данных в формате XML, который вы можете записать в виде потока. Если у вас очень простой формат данных, может подойти что-то вроде CSV.Не использовать
NSData
для загрузки в Dropbox. Вместо этого запишите свои данные в файл (см. Выше) и укажите Dropbox SDK на этот файл. Dropbox SDK делает это довольно легко (DBRestClient
имеетuploadFile:toPath:fromPath:
метод).Если ваша модель данных затрудняет использование потокового подхода, попробуйте разбить данные на более управляемые части. Затем вы можете использовать свой старый метод сериализации словарей, просто с несколькими файлами.
Будьте осторожны с использованием памяти Core Data. Попытайтесь повторно исправить ошибки объектов, используя
refreshObject:mergeChanges:
если возможно, разбить циклические ссылки в ваших данных (подробности см. в Руководстве по программированию основных данных).Избегайте использования пулов автоматического выпуска, пока вы находитесь в длительном цикле, или создайте отдельный
NSAutoreleasePool
это истощается в каждой итерации вашего цикла.
Способ обойти этот тип нехватки памяти заключается в создании API-интерфейсов с использованием потоков, как для записи преобразованных данных в файл на диске, так и для загрузки данных в веб-службу.
Во время конвертации вы можете использовать NSOutputStream
записывать куски данных в файл, чтобы избежать одновременного хранения большого объема данных в памяти. Затем, NSMutableURLRequest
может принять NSStream
для тела вместо NSData
так что вы должны создать NSInputStream
прочитать из вашего файла обратно с диска и загрузить его.
Использование потоков таким образом гарантирует, что вы никогда не загрузите более 50 МБ данных, и должно избегать предупреждений о памяти, которые вы видите.