Лучший способ обработки многих ASIHttpRequests и основных операций с данными в приложении для iPad.

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

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

Для каждого объекта в настоящее время я использую ASINetworkQueue для постановки в очередь 15 запросов изображений. Когда очередь завершается, я создаю эскиз объекта с его изображениями для отображения в сетке, а затем сохраняю все png-файлы в основные данные.

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

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

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


person nsx241    schedule 20.05.2011    source источник
comment
@nsx241 nsx241 Вы пытались сохранить изображения на диск и сохранить только имена файлов в основных данных?   -  person Nick Weaver    schedule 20.05.2011
comment
Изображения сохраняются, поскольку мы хотим, чтобы они также были доступны в автономном режиме. Эти изображения на самом деле динамически генерируются сторонним механизмом построения диаграмм на основе данных клиентов, поэтому нам нужно запрашивать у сервера последние данные, если мы находимся в сети.   -  person nsx241    schedule 20.05.2011
comment
@ nsx241 я имел в виду: вы храните изображения в виде больших двоичных объектов с основными данными или сохраняете их на диске?   -  person Nick Weaver    schedule 20.05.2011
comment
@ nsx241 интересно, я написал imageLoader, используя очередь операций в сочетании с ASIHTTPRequests для tableView, который загружает семь изображений при начальном просмотре, однако, когда пользователь быстро прокручивает вниз, очередь легко заполняется 40 запросами. Никакой блокировки не происходит. Вы должны выяснить, где узкое место.   -  person Nick Weaver    schedule 20.05.2011
comment
Вероятно, это комбинация сетевых запросов и основных данных, но на самом деле я отправляю около 400 запросов — 30 объектов x 15 изображений.   -  person nsx241    schedule 21.05.2011
comment
Вы ставите запросы в очередь или просто запускаете их в цикле с помощью startAsynchronous?   -  person Nick Weaver    schedule 21.05.2011
comment
Я ставлю в очередь запросы изображений для каждого объекта, поэтому в худшем случае это 30 очередей (ASINetworkQueue) с 15 запросами в каждой очереди. Все очереди запускаются (вызов идет) в цикле.   -  person nsx241    schedule 21.05.2011
comment
@nsx241 использовали инструменты, чтобы выяснить, какой процесс потребляет время процессора? Я не могу сказать наверняка, но 30 очередей выглядят не очень хорошо, вы должны использовать меньше очередей.   -  person Nick Weaver    schedule 21.05.2011


Ответы (3)


Прежде всего, избегайте хранения больших BLOB-объектов в Core Data, сохранение эскизов — это нормально (хотя вы должны оптимизировать свою модель для этого), но вы должны хранить полное изображение после его реконструкции в папке «Документы».

Вам обязательно следует использовать очередь, NSOperationQueue или сетевую очередь ASI. Я делаю что-то подобное в своем приложении, которое имеет несколько зависимостей. Таким образом, для каждого из 30 объектов вам нужно, чтобы блок (или рабочая функция) вызывался при загрузке 15 изображений. В идеале вы хотите сделать эту работу вне основного потока. Соберите все эти требования вместе, и я бы сказал, что вам нужно как минимум две очереди, одна для сетевых запросов и одна для рабочих блоков, и вы должны использовать NSBlockOperations, что значительно упрощает все это. Итак, код будет примерно таким...

// Loop through the objects
for (NSArray *objectParts in objectsToDownload) {

    // Create our Object
    Object *obj = [Object insertIntoManagedObjectContext:self.moc];

    // This is the block which will do the post processing for the object
    NSBlockOperation *processBlock = [NSBlockOperation blockOperationWithBlock:^{

        // Do post processing here, be very careful with multi-threading CoreData
        // it's likely you'll need some class to dispatch you MOCs which have all
        // all the observers set up.

        // We're gonna assume that all the sub-images have been stored in a instance
        // variable:
        [obj performPostProcessing];

    }];

    // Given the list of 15 images which form each object
    for (NSURL *part in objectParts) {

        // Create the ASI request for this part
        ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:part];

        // Configure the request
        [request setDelegate:self];
        [request setDidFailSelector:@selector(partRequestDidFail:)];
        [request setDidFinishSelector:@selector(partRequestDidFinish:)];

        // Store the object in the UserInfo dictionary
        NSMutableDictionary *userInfo = [NSMutableDictionary dictionaryWithObjectsAndKeys:obj, @"Object", nil];
        [request setUserInfo:userInfo];

        // Add it as a dependency
        [processBlock addDependency:request];

        // Add it to our network queue
        [networkQueue addOperation:request];
    }

    // Add the processBlock to our worker queue
    [workerQueue addOperation:processBlock];
}

Затем вам также нужно будет написать методы делегата, didFinish будет выглядеть примерно так...

- (void)partRequestDidFinish:(ASIHTTPRequest *)request {
    // Remember this is one the main thread, so any heavy lifting should be 
    // put inside a block operation, and queued, which will complicate the 
    // dependencies somewhat, but is possible.

    // Get the result data
    NSData *data = [request responseData];

    // Get the object that it belongs to from the user info dic
    Object *obj = [[request userInfo] objectForKey:@"Object"];

    // Keep track of the partial data in the object
    [obj storePartialDataForPostProcessing:data];
}

И все это войдет в ваш класс, который подключается к вашему серверу и создает ваши объекты, так что это не контроллер представления или что-то еще, а просто обычный подкласс NSObject. Он должен будет иметь две очереди, контекст управляемого объекта (и, скорее всего, метод, который возвращает другой MOC для использования в потоках, что-то вроде этого:

// Fends a MOC suitable for use in the NSBlockOperations
- (NSManagedObjectContext *)moc {
    // Get a blank managed object context
    NSManagedObjectContext *aContext = [[UIApplication sharedApplication] managedObjectContext;
[aContext setMergePolicy:NSMergeByPropertyStoreTrumpMergePolicy];
    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
    [nc addObserver:self selector:@selector(mergeChangesFromMOC:) name:NSManagedObjectContextDidSaveNotification object:aContext];
    return aContext;

}

- (void)mergeChangesFromMOC:(NSNotification *)aNotification {
    @try {
        [self.managedObjectContext mergeChangesFromContextDidSaveNotification:aNotification];
        NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
        [nc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:[aNotification object]];      
    }
    @catch (NSException * e) {
        NSLog(@"Stopping on exception: %@", [e description]);
    }
    @finally {}
}

Вам также нужно каким-то образом отслеживать прогресс, повторно ставить в очередь неудачные загрузки, отменять и сохранять MOC в конце. Повторная постановка в очередь неудачных загрузок довольно сложна. Во всяком случае, надеюсь, что это поможет.

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

person Daniel Thorpe    schedule 14.06.2011
comment
Спасибо. Это был подход, на который я смотрел в следующей версии. В основном одна очередь для запросов и другая для обработки. Однако я не рассматривал блочные операции, но они, похоже, очень хорошо подходят для того, что мне нужно выполнить. Обязательно постараюсь их использовать. - person nsx241; 15.06.2011
comment
Это безумно круто. Спасибо! - person GordyD; 10.10.2011

Начнем с того, что 1 очереди должно быть достаточно для всех запросов изображений.

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

Что касается блокировки, необходимо учитывать несколько моментов с изображениями:

  1. Они сжаты, поэтому их нужно раздуть, прежде чем они станут UIImages, что очень сильно нагружает ЦП.
  2. Если вы когда-нибудь захотите записать в файловую систему, этот процесс будет заблокирован. Сделайте это в другой очереди, чтобы избежать блокировки.
  3. Никогда не рекомендуется хранить Blob в CoreData, сохранять путь к файлу в виде строки в Core Data и извлекать его с диска с помощью очереди.

Простое использование 3 разных NSOperationQueue сделает ваше приложение намного более отзывчивым: 1 для ASIHTTPRequests (не создавайте новый, используйте значение по умолчанию с startAsynchronous) 1 для записи изображения на диск 1 для выборки изображения с диска

person Pier-Olivier Thibault    schedule 23.05.2011

Поскольку вы будете отображать изображение для просмотра, почему бы вам не использовать SDwebImage:

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

[imageView setImageWithURL:[NSURL URLWithString:@"http://www.domain.com/path/to/image.jpg"] placeholderImage:[UIImage imageNamed:@"placeholder.png"]]

https://github.com/rs/SDWebImage

person zhouhsc_cn    schedule 09.03.2012