Заполнение NSImage данными из асинхронного NSURLConnection - PullRequest
5 голосов
/ 16 ноября 2009

Я наткнулся на пресловутую стену, пытаясь выяснить, как заполнить NSImage данными, возвращаемыми из асинхронного NSURLConnection в моем настольном приложении (НЕ приложении iPhone !!).

Вот ситуация.

У меня есть таблица, в которой используются пользовательские ячейки. В каждой пользовательской ячейке есть NSImage, который извлекается из веб-сервера. Для заполнения изображения я могу легко выполнить синхронный запрос:

myThumbnail = [[NSImage alloc] initWithContentsOfFile:myFilePath];

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

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

Проблема, с которой я столкнулся, заключается в том, что, как только у меня появятся данные, мне потребуется обратный вызов в мой вызывающий класс (настраиваемое представление таблицы).

У меня сложилось впечатление, что я мог бы сделать что-то подобное, но это не сработало, потому что (я предполагаю), что мой вызывающий класс действительно нуждается в делегате:

NSImage * myIMage;
myImage = [myConnectionClass getMyImageMethod];

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

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    // do something with the data
    // receivedData is declared as a method instance elsewhere
    NSLog(@"Succeeded! Received %d bytes of data",[receivedData length]);

    // release the connection, and the data object
    [connection release];
    [receivedData release];
}

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

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

Любая помощь будет принята с благодарностью.

Обновление решения

На случай, если кому-то еще понадобится подобное решение, благодаря помощи Бена, вот что я придумал (в общем, модифицированный для публикации, конечно). Имейте в виду, что я также реализовал пользовательское кэширование изображений и сделал свой класс загрузки изображений достаточно универсальным, чтобы его можно было использовать в разных местах моего приложения для вызова изображений.

В моем методе вызова, который в моем случае был пользовательской ячейкой в ​​таблице ...

ImageLoaderClass * myLoader = [[[ImageLoaderClass alloc] init] autorelease];

    [myLoader fetchImageWithURL:@"/my/thumbnail/path/with/filename.png"
                     forMethod:@"myUniqueRef"
                        withId:1234
                   saveToCache:YES
                     cachePath:@"/path/to/my/custom/cache"];

Это создает экземпляр класса myLoader и передает ему 4 параметра. URL-адрес изображения, которое я хочу получить, уникальная ссылка, которую я использую, чтобы определить, какой класс выполнял вызов при настройке наблюдателей уведомлений, идентификатор изображения, хочу ли я сохранить изображение в кэш или нет, а также путь в кеш.

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

-(void)fetchImageWithURL:(NSString *)imageURL 
           forMethod:(NSString *)methodPassed 
              withId:(int)imageIdPassed 
         saveToCache:(BOOL)shouldISaveThis
           cachePath:(NSString *)cachePathToUse
{

    NSURLRequest *theRequest=[NSURLRequest requestWithURL:[NSURL URLWithString:imageURL]
                                          cachePolicy:NSURLRequestUseProtocolCachePolicy
                                      timeoutInterval:60.0];

    // Create the connection with the request and start loading the data

    NSURLConnection *theConnection=[[NSURLConnection alloc] initWithRequest:theRequest delegate:self];

    if (theConnection) {

        // Create the NSMutableData that will hold
        // the received data 
        // receivedData is declared as a method instance elsewhere

        receivedData = [[NSMutableData data] retain];

        // Now set the variables from the calling class

        [self setCallingMethod:methodPassed];
        [self setImageId:imageIdPassed];
        [self setSaveImage:shouldISaveThis];
        [self setImageCachePath:cachePathToUse];

    } else {
        // Do something to tell the user the image could not be downloaded
    }
}

В методе connectionDidFinishLoading я сохранил файл в кеш, если это было необходимо, и отправил уведомление любому слушающему наблюдателю:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
    NSLog(@"Succeeded! Received %d bytes of data",[receivedData length]);

    // Create an image representation to use if not saving to cache
    // And create a dictionary to send with the notification    

    NSImage * mImage = [[NSImage alloc ] initWithData:receivedData];
    NSMutableDictionary * mDict = [[NSMutableDictionary alloc] init];

    // Add the ID into the dictionary so we can reference it if needed

    [mDict setObject:[NSNumber numberWithInteger:imageId] forKey:@"imageId"];

    if (saveImage)
    {
        // We just need to add the image to the dictionary and return it
        // because we aren't saving it to the custom cache

        // Put the mutable data into NSData so we can write it out

        NSData * dataToSave = [[NSData alloc] initWithData:receivedData];

        if (![dataToSave writeToFile:imageCachePath atomically:NO])
    NSLog(@"An error occured writing out the file");
    }
    else
    {
        // Save the image to the custom cache

        [mDict setObject:mImage forKey:@"image"];
    }

    // Now send the notification with the dictionary

    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

    [nc postNotificationName:callingMethod object:self userInfo:mDict];

    // And do some memory management cleanup

    [mImage release];
    [mDict release];
    [connection release];
    [receivedData release];
}

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

-(id)init
{
    [super init];

    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

    [nc addObserver:self selector:@selector(updateCellData:) name:@"myUniqueRef" object:nil];

    return self;
}

Проблема решена!

Ответы [ 3 ]

3 голосов
/ 09 ноября 2011

Мое решение состоит в том, чтобы использовать Grand Central Dispatch (GCD) для этой цели, вы также можете сохранить образ на диск в строке после того, как получили его с сервера.

- (NSView *)tableView:(NSTableView *)_tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row
{
    SomeItem *item = [self.items objectAtIndex:row];
    NSTableCellView *cell = [_tableView makeViewWithIdentifier:tableColumn.identifier owner:self];
    if (item.artworkUrl)
    {
        cell.imageView.image = nil;
        dispatch_async(dispatch_queue_create("getAsynchronIconsGDQueue", NULL), 
        ^{
            NSURL *url = [NSURL URLWithString:item.artworkUrl];
            NSImage *image = [[NSImage alloc] initWithContentsOfURL:url];
            cell.imageView.image = image;        
        });
    }
    else
    {
        cell.imageView.image = nil;    
    }
    return cell;
}

(Я использую автоматический подсчет ссылок (ARC), поэтому нет сохранения и освобождения.)

1 голос
/ 16 ноября 2009

Ваша интуиция верна; Вы хотите получить обратный вызов от объекта, который является делегатом NSURLConnection контроллеру, который управляет табличным представлением, который обновит ваш источник данных, а затем вызовет -setNeedsDisplayInRect: с прямоугольником строки, которой соответствует изображение.

0 голосов
/ 16 ноября 2009

Вы пробовали использовать метод initWithContentsOfURL:?

...