Эффективно импортирующие данные

Эта статья описывает, как можно эффективно импортировать данные в Базовое Применение данных и превратить данные в управляемые объекты сохранить к персистентному хранилищу. Это обсуждает некоторые фундаментальные образцы Какао, за которыми необходимо следовать, и образцы, которые являются определенными для Базовых Данных.

Основные принципы какао

Вместе со многими другими ситуациями при использовании Базовых Данных для импорта файла данных важно помнить, что “применяются нормальные правила” разработки приложений Какао. При импорте файла данных, который необходимо проанализировать в некотором роде, вероятно, что Вы создадите большое количество временных объектов. Они могут привести много в рабочее состояние памяти и привести к разбивке на страницы. Когда Вы были бы с неосновным Применением данных, можно использовать локальные блоки пула автовыпуска для надевания связанного, сколько дополнительных объектов находится в памяти. Для больше о взаимодействии между Базовыми Данными и управлением памятью, посмотрите Сокращающую Память Наверху.

Необходимо также избежать повторять работу излишне. Один тонкий случай находится в создании предиката, содержащего переменную. При создании предиката как показано в следующем примере Вы не только создаете предикат каждый раз через Ваш цикл, Вы анализируете тот.

// Loop over employeeIDs.
for (NSString *anID in employeeIDs) {
    NSString *predicateString = [NSString stringWithFormat:@"employeeID == %@", anID];
 
    NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];

Для создания предиката из отформатированной строки платформа должна проанализировать строку и создать экземпляры объектов выражения и предиката. Если Вы используете ту же форму предиката много раз, но изменяете значение одного из выражений постоянного значения на каждом использовании, более эффективно создать предикат один раз и затем использовать подстановку переменных (см. Предикаты Создания). Этот метод проиллюстрирован в следующем примере.

NSString *predicateString = [NSString stringWithFormat @"employeeID == $EMPLOYEE_ID"];
NSPredicate *predicate = [NSPredicate predicateWithFormat:predicateString];
 
for (NSString *anID in employeeIDs) {
    NSDictionary *variables = @{ @"EMPLOYEE_ID" : anID };
    NSPredicate *localPredicate = [predicate predicateWithSubstitutionVariables:variables];

Сокращение пикового объема потребляемой памяти

При импорте большого объема данных в Базовое Применение данных необходимо удостовериться, что Вы поддерживаете пиковый объем потребляемой памяти своего приложения на низком уровне путем импорта данных в пакетах и чистки Базового Стека данных между пакетами. Соответствующие проблемы и методы обсуждены в Базовой Производительности Данных (особенно Сокращающий Память Наверху) и Объектное Пожизненное управление, но они получены в итоге здесь для удобства.

Импорт в пакетах

Во-первых, необходимо обычно создавать отдельный контекст управляемого объекта для импорта и устанавливать его менеджера по отмене в nil. (Контексты не являются особенно дорогими для создания, поэтому при кэшировании персистентного координатора хранилища, можно использовать различные контексты для различных рабочих наборов или отличных операций.)

NSManagedObjectContext *importContext = [[NSManagedObjectContext alloc] init];
NSPersistentStoreCoordinator *coordinator = <#Get the coordinator#>;
[importContext setPersistentStoreCoordinator:coordinator];
[importContext setUndoManager:nil];

(Если у Вас есть существующий Базовый Стек данных, можно получить персистентного координатора хранилища от другого контекста управляемого объекта.) Установка менеджера по отмене к nil средние значения, что:

  1. Вы не тратите впустую усилие, записывающее действия отмены для изменений (таких как вставки), который не будет отменен;

  2. Менеджер по отмене не поддерживает сильные ссылки к измененным объектам и тем самым препятствует тому, чтобы они были освобождены (см. управление Изменением и Отменой).

Необходимо импортировать данные и создать соответствующие управляемые объекты в пакетах (оптимальный размер пакета будет зависеть от того, сколько данных связано с каждой записью и как низко Вы хотите сохранить объем потребляемой памяти). Вы обрабатываете пакеты в блоке пула автовыпуска; в конце каждого пакета необходимо сохранить контекст управляемого объекта (использование save:). (Пока Вы не сохраняете, контекст должен, сохраняет сильные ссылки ко всем незаконченным изменениям, которые Вы внесли во вставленные объекты.)

Контакт с циклами сильной ссылки

Управляемые объекты с отношениями почти всегда создают неисправимые циклы сильной ссылки. Во время импорта при создании отношений между объектами необходимо повредить циклы так, чтобы объекты могли быть освобождены, когда они больше не необходимы. Чтобы сделать это, можно или превратить объекты в отказы или сбросить целый контекст. Для полного обсуждения посмотрите Повреждающиеся Циклы Сильной ссылки Отношения.

Реализация, находить-или-создавать эффективно

Общий метод при импорте данных должен следовать за образцом «находить-или-создавать», где Вы устанавливаете некоторые данные, от которых можно создать управляемый объект, определить, существует ли управляемый объект уже, и создайте его, если это не делает.

Существует много ситуаций, где Вы, возможно, должны найти существующие объекты (объекты уже сохраненный в хранилище) для ряда дискретных входных значений. Простое решение должно создать цикл, затем для каждого значения поочередно выполняют выборку, чтобы определить, существует ли соответствующий сохраненный объект и т.д. Этот образец не масштабируется хорошо. При профилировании приложения с этим образцом Вы обычно находите, что выборка одна из более дорогих операций в цикле (по сравнению только с итерацией по набору элементов). Еще хуже, этот образец поворачивается O(n) проблема в O(n^2) проблема.

Намного более эффективно — когда возможный — создать все управляемые объекты в единственной передаче, и затем согласовать любые отношения во второй передаче. Например, при импорте данных, которые Вы знаете, не содержит копий (скажите, потому что Ваш набор исходных данных пуст), можно просто создать управляемые объекты представлять данные и не сделать любые поиски вообще. Или если Вы импортируете «плоские» данные без отношений, можно создать управляемые объекты для всего набора и избавиться (удаляют) любые копии, прежде чем сохранят использование большого сингла IN предикат.

Если действительно необходимо следовать, образец находить-или-создавать — говорят, потому что Вы импортируете гетерогенные данные, где информация об отношении смешана в с информацией атрибута — можно оптимизировать, как Вы находите существующие объекты путем сокращения к минимуму количества выборок, которые Вы выполняете. Как выполнить, это зависит от суммы справочных данных, с которыми необходимо работать. Если Вы импортируете 100 потенциально новых объектов, и только имеете 2000 в Вашей базе данных, выбирание всех существующих и кэширование их могут не представлять значительный штраф (особенно, если необходимо выполнить работу несколько раз). Однако, если у Вас есть 100 000 элементов в Вашей базе данных, давлении памяти хранения, кэшируемые могут препятствовать.

Можно использовать комбинацию IN предикат и сортирующий для сокращения использование Базовых Данных к единственному запросу выборки. Предположим, например, Вы хотите взять список сотрудника IDs (как строки) и уже создать записи Сотрудника для всех те не в базе данных. Рассмотрите этот код, где Сотрудник является объектом с a name атрибут, и listOfIDsAsString список IDs, для которого Вы хотите добавить объекты, если они уже не существуют в хранилище.

Во-первых, отделитесь и вид IDs (строки) интереса.

// get the names to parse in sorted order
NSArray *employeeIDs = [[listOfIDsAsString componentsSeparatedByString:@"\n"]
        sortedArrayUsingSelector: @selector(compare:)];

Затем, создайте использование предиката IN с гарантирующим массивом строк имени и дескриптором вида результаты возвращаются с той же сортировкой как массив строк имени. ( IN эквивалентно SQL IN работа, где левая сторона должна появиться в наборе, указанном правой стороной.)

// Create the fetch request to get all Employees matching the IDs.
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:
        [NSEntityDescription entityForName:@"Employee" inManagedObjectContext:aMOC]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat:@"(employeeID IN %@)", employeeIDs]];
 
// make sure the results are sorted as well
[fetchRequest setSortDescriptors:
        @[[[NSSortDescriptor alloc] initWithKey: @"employeeID" ascending:YES]]];

Наконец, выполните выборку.

NSError *error;
NSArray *employeesMatchingNames = [aMOC executeFetchRequest:fetchRequest error:&error];

Вы заканчиваете с двумя сортированными массивами — один с сотрудником, которого IDs передал в запрос выборки, и один с управляемыми объектами, соответствовавшими им. Для обработки их Вы обходите сортированные списки, выполняющие эти шаги:

  1. Получите следующий ID и Сотрудника. Если ID не соответствует Сотруднику ID, создайте нового Сотрудника для того ID.

  2. Получите следующего Сотрудника: если соответствие IDs, переместитесь в следующий ID и Сотрудника.

Независимо от того, сколько IDs Вы передаете в, Вы только выполняете единственную выборку, и остальное просто обходит набор результатов.

Упоминание ниже показывает полный код для примера в предыдущем разделе.

// Get the names to parse in sorted order.
NSArray *employeeIDs = [[listOfIDsAsString componentsSeparatedByString:@"\n"]
        sortedArrayUsingSelector: @selector(compare:)];
 
// create the fetch request to get all Employees matching the IDs
NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
[fetchRequest setEntity:
        [NSEntityDescription entityForName:@"Employee" inManagedObjectContext:aMOC]];
[fetchRequest setPredicate: [NSPredicate predicateWithFormat: @"(employeeID IN %@)", employeeIDs]];
 
// Make sure the results are sorted as well.
[fetchRequest setSortDescriptors:
    @[ [[NSSortDescriptor alloc] initWithKey: @"employeeID" ascending:YES] ]];
// Execute the fetch.
NSError *error;
NSArray *employeesMatchingNames = [aMOC executeFetchRequest:fetchRequest error:&error];