Значительная задержка при загрузке изображения с использованием UIImage из URL асинхронно - PullRequest
5 голосов
/ 30 ноября 2010

Я пытаюсь написать приложение для iPad, которое загружает изображение с URL.Я использую следующий код загрузки изображения:

    url = [NSURL URLWithString:theURLString];
    NSData *data = [NSData dataWithContentsOfURL:url];
    img = [[UIImage alloc] initWithData:data];
    [imageView setImage:img];
    [img release];
    NSLog(@"Image reloaded");

Весь этот код добавляется в NSOperationQueue в качестве операции, поэтому он будет загружаться асинхронно и не заставлять мое приложение блокироваться, если веб-сервер изображения медленный.Я добавил строку NSLog, чтобы видеть в консоли, когда этот код завершит выполнение.

Я постоянно замечал, что изображение обновляется в моем приложении примерно через 5 секунд ПОСЛЕ того, как код завершает работу.Однако, если я использую этот код сам по себе, не помещая его в NSOperationQeue, кажется, что изображение обновляется почти сразу.

Эта задержка не полностью вызвана медленным веб-сервером ... Я могу загрузить URL-адрес изображения в Safari, и его загрузка занимает менее секунды, или я могу загрузить его с тем же кодом без NSOperationQueueи загружается намного быстрее.

Есть ли способ уменьшить задержку перед отображением моего изображения, но продолжать использовать NSOperationQueue?

Ответы [ 2 ]

6 голосов
/ 30 ноября 2010

Согласно документации, написанный вами код недействителен.Объекты UIKit нельзя вызывать нигде, кроме как в главном потоке.Могу поспорить, что то, что вы делаете, в большинстве случаев работает, но не приводит к успешному изменению дисплея, а экран обновляется по совпадению по какой-то другой причине.

Apple настоятельно рекомендует, чтобы потоки неспособ выполнять асинхронные выборки URL, если вы хотите сохранить заряд батареи.Вместо этого вы должны использовать NSURLConnection и позволить runloop организовать асинхронное поведение.Не так сложно написать быстрый метод, который просто накапливает данные в NSData, поскольку он затем передает все это делегату, когда соединение установлено, но при условии, что вы предпочитаете придерживаться того, что у вас есть, я бы порекомендовал:

url = [NSURL URLWithString:theURLString];
NSData *data = [NSData dataWithContentsOfURL:url];
[self performSelectorOnMainThread:@selector(setImageViewImage:) withObject:data waitUntilDone:YES];

...

- (void)setImageViewImage:(NSData *)data
{
    img = [[UIImage alloc] initWithData:data];
    [imageView setImage:img];
    [img release];
    NSLog(@"Image reloaded");
}

performSelectorOnMainThread делает то, что говорит имя - объект, на который отправляется объект, будет планировать запрошенный селектор с объектом, заданным как один параметр в главном потоке, как только цикл выполнения сможет добраться доЭто.В этом случае «data» - это объект с автоматическим освобождением в пуле в потоке, неявно созданном NSOperation.Поскольку вам нужно, чтобы оно оставалось действительным до тех пор, пока оно не будет использовано, я использовал waitUntilDone:YES.Альтернативой может быть сделать данные чем-то, что у вас есть, и метод основного потока освободит их.

Основным недостатком этого метода является то, что если изображение возвращается в сжатом виде (например, JPEG илиPNG), он будет распакован в основном потоке.Чтобы избежать этого, не делая эмпирических предположений о поведении UIImage, которое выходит за рамки того, что задокументировано как безопасное, вам нужно перейти на уровень C и использовать CoreGraphics.Но я принимаю это как данность, что это выходит за рамки этого вопроса.

1 голос
/ 01 декабря 2010

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

Вы сможете использовать исходный код как есть, но просто измените [imgViewsetImage: img] в:

[imageView performSelectorOnMainThread:@selector(setImage:)
                          withObject:img
                       waitUntilDone:NO];
...