Импорт больших наборов данных на iPhone с использованием CoreData - PullRequest
5 голосов
/ 26 января 2010

У меня очень неприятная проблема. Приложение для iPhone загружает данные с сетевого сервера. Данные отправляются в виде plist, и при анализе они должны быть сохранены в базе данных SQLite с использованием CoreData.

Проблема в том, что в некоторых случаях эти наборы данных слишком велики (более 5000 записей), а импорт занимает слишком много времени. Подробнее об этом, когда iPhone пытается приостановить экран, Watchdog убивает приложение, потому что оно все еще обрабатывает импорт и не отвечает до 5 секунд, поэтому импорт никогда не заканчивается.

Я использовал все рекомендуемые методы в соответствии со статьей «Эффективный импорт данных» http://developer.apple.com/mac/library/DOCUMENTATION/Cocoa/Conceptual/CoreData/Articles/cdImporting.html и другими документами, касающимися этого, но все еще очень медленно.

Решение, которое я ищу, состоит в том, чтобы позволить приложению приостановить работу, но позволить запуску импорта позади (лучше) или предотвратить попытки приостановить приложение вообще. Или приветствуется любая лучшая идея.

Любые советы о том, как преодолеть эти проблемы, высоко ценятся! Спасибо

Ответы [ 9 ]

4 голосов
/ 27 января 2010

Во-первых, если вы можете упаковать данные с приложением, которое было бы идеально.

Однако, если вы не можете этого сделать, я бы сделал следующее:

  1. После загрузки данных разбейте их на несколько файлов до импорта.
  2. Импорт в фоновом потоке, по одному файлу за раз.
  3. Как только файл был импортирован и сохранен, удалите файл импорта.
  4. При запуске найдите файлы, ожидающие обработки, и выберите, где вы остановились.

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

4 голосов
/ 26 января 2010

Вместо отправки файлов plist на телефон, вы можете отправить готовые к использованию файлы sqlite. Это имеет много преимуществ:

  1. нет необходимости импортировать на телефон
  2. более компактный

Если вы всегда заменяете весь контент, просто перезапишите постоянное хранилище на устройстве. В противном случае вы можете сохранить массив как plist для всех скачанных вами sqlites, а затем использовать его для добавления всех хранилищ в persistentStoreCoordinator.

Итог: используйте несколько предварительно скомпилированных файлов sqlite и добавьте их в persistentStoreCoordinator.

Вы можете использовать iPhone Simulator для создания этих CoreData-SQLite-Stores или использовать отдельное приложение для Mac. Вам нужно будет написать оба из них самостоятельно.

2 голосов
/ 26 января 2010

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

Это в основном ViewControllers viewDidLoad

- (void)viewDidLoad 
{
    [super viewDidLoad];

    NSError *error = nil;
    if (![[self fetchedResultsController] performFetch:&error]) {
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    // Only insert those not imported, here I know it should be 2006 entries
    if ([self tableView:nil numberOfRowsInSection:0] != 2006) {

        // Put up an alert with a progress bar, need to implement
        [self createProgressionAlertWithMessage:@"Initilizing database"];  

        // Spawn the insert thread making the app still "live" so it 
        // won't be killed by the OS
        [NSThread detachNewThreadSelector:@selector(loadInitialDatabase:) 
                                 toTarget:self 
                      withObject:[NSNumber numberWithInt:[self tableView:nil 
                                                numberOfRowsInSection:0]]];
    }
}

Нить вставки была сделана так

- (void)loadInitialDatabase:(NSNumber*)number
{
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

    int done = [number intValue]+1; // How many done so far

    // I load from a textfile (csv) but imagine you should be able to 
    // understand the process and make it work for your data
    NSString *file = [NSString stringWithContentsOfFile:[[NSBundle mainBundle]
                                                pathForResource:@"filename"
                                                         ofType:@"txt"] 
                                               encoding:NSUTF8StringEncoding
                                                  error:nil];

    NSArray *lines = [file componentsSeparatedByString:@"\n"];

    float num = [lines count];
    float i = 0;
    int perc = 0;

    for (NSString *line in lines) {
        i += 1.0;

        if ((int)(i/(num*0.01)) != perc) {
            // This part updates the alert with a progress bar
            // setProgressValue: needs to be implemented 
            [self performSelectorOnMainThread:@selector(setProgressValue:) 
                                   withObject:[NSNumber numberWithFloat:i/num] 
                                waitUntilDone:YES]; 
            perc = (int)(i/(num*0.01));
        }

        if (done < i) // keep track of how much done previously
            [self insertFromLine:line]; // Add to data storage...

    }

    progressView = nil;
    [progressAlert dismissWithClickedButtonIndex:0 animated:YES]; 
    [pool release];
}

Это немного грубо, поэтому он пытается запустить хранилище данных с того места, где оно осталось, если пользователь попытался остановить его в предыдущие разы ...

1 голос
/ 16 февраля 2011

У меня была похожая проблема с импортом многих объектов в CoreData. Сначала я делал save в контексте управляемого объекта после каждого объекта, который хотел создать и вставить.

Что вам нужно сделать, это создать / инициализировать каждый объект, который вы хотите сохранить в CoreData, и после того, как вы перебрали все свои удаленные данные + создали объекты, выполните контекст управляемого объекта save.

Полагаю, вы могли бы рассматривать это как выполнение транзакции в базе данных SQLite: начать транзакцию, выполнить много операций вставки / обновления, завершить транзакцию.

если это все еще слишком долго, просто выполните чертову задачу и предотвратите взаимодействие с пользователем до завершения

0 голосов
/ 16 декабря 2013

Я работаю над приложением, которое регулярно обрабатывает вставки, удаления и обновления по 100 КБ с помощью Core Data. Если он задыхается от вставок 5К, необходимо провести некоторую оптимизацию.

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

- (void)main
{
  NSManagedObjectContext *ctx = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
  [ctx setPersistentStoreCoordinator:mainManagedObjectContext.persistentStoreCoordinator];
  [ctx setUndoManager:nil];
  // Do your insertions here!
  NSError *error = nil;
  [ctx save:&error];
}

Учитывая ваши обстоятельства, я не верю, что вам нужен менеджер по отмене. Наличие одного приведет к снижению производительности, потому что Core Data отслеживает ваши изменения.

Используйте ЭТОТ контекст, чтобы выполнить все ваши действия CRUD в методе -main, затем сохраните этот контекст управляемого объекта. Что бы ни владел контекстом управляемого объекта вашего основного потока, он должен зарегистрироваться, чтобы ответить на NSNotification с именем NSManagedObjectContextDidSaveNotification. Зарегистрируйтесь так:

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mocDidSaveNotification:) name:NSManagedObjectContextDidSaveNotification object:nil];

Затем определите этот селектор:

- (void)mocDidSaveNotification:(NSNotification *)notification
{
  NSManagedObjectContext *ctx = [notification object];
  if (ctx == mainManagedObjectContext) return;
  [mainManagedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}

Когда все это объединится, это позволит вам выполнять длительные операции с фоновыми потоками, не блокируя поток пользовательского интерфейса. Существует несколько вариантов этой архитектуры, но центральная тема такова: обработка в потоке BG, объединение в основном потоке, обновление вашего пользовательского интерфейса. Некоторые другие вещи, которые нужно иметь в виду: (1) сохраняйте пул авто-релизов во время обработки и время от времени истощайте его, чтобы снизить потребление памяти. В нашем случае мы делаем это каждые 1000 объектов. Приспособьтесь к своим потребностям, но имейте в виду, что слив может быть дорогостоящим в зависимости от объема памяти, требуемого для объекта, поэтому вы не хотите делать это слишком часто. (2) постарайтесь сократить ваши данные до абсолютного минимума, необходимого для работы функционального приложения. Сокращая объем данных для анализа, вы сокращаете время, необходимое для их сохранения. (3) используя этот многопоточный подход, вы можете одновременно обрабатывать ваши данные. Поэтому создайте 3-4 экземпляра вашего подкласса NSOperation, каждый из которых обрабатывает только часть данных, чтобы они все выполнялись одновременно, что приводит к меньшему количеству реального времени, затрачиваемого на анализ набора данных.

0 голосов
/ 26 марта 2010

Допустим, что Restful (отложенная загрузка) не вариант ... Я так понимаю, вы хотите повторить. Если проблема загрузки имеет тип «все меньше и меньше загружается строк во все большее и большее время), то в коде psuedo ...

[self sQLdropIndex(OffendingIndexName)]
[self breathInOverIP];
[self breathOutToSQLLite];
[self sQLAddIndex(OffendingIndexName)]

Это должно сказать вам много.

0 голосов
/ 05 марта 2010

Есть ли возможность настроить серверную часть для предоставления веб-службы RESTful для обработки ваших данных? У меня была похожая проблема, и я смог раскрыть свою информацию через веб-сервис RESTful. На iphone есть несколько библиотек, которые делают чтение из веб-сервиса таким простым. Я решил запросить JSON у службы и использовал библиотеку SBJSON на iphone, чтобы быстро получить полученные результаты и преобразовать их в словари для удобства использования. Я использовал библиотеку ASIHTTP для создания веб-запросов, организации очередей последующих запросов и их запуска в фоновом режиме.

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

0 голосов
/ 26 января 2010

Я полагаю, вы не показываете все записи 5K клиенту? Я бы порекомендовал выполнить всю необходимую вам агрегацию на сервере, а затем только отправлять необходимые данные на телефон. Даже если это потребует создания нескольких разных представлений данных, это все равно будет на несколько порядков быстрее, чем отправка (а затем обработка) всех этих строк в iPhone.

Вы также обрабатываете данные в отдельном (не событийном / пользовательском) потоке?

0 голосов
/ 26 января 2010

Есть ли способ упаковать данные заранее - скажем, во время разработки? А когда вы отправляете приложение в магазин, некоторые данные уже есть? Это сократит объем данных, которые вы должны извлечь, что поможет решить эту проблему?

Если данные чувствительны ко времени, или не готовы, или по какой-либо причине вы не можете этого сделать, могли бы вы сжать данные с помощью zlib-сжатия перед отправкой по сети?

Или проблема в том, что телефон умирает при 5K + вставках?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...