Я столкнулся с пресловутой стеной, пытаясь понять, как заполнить 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;
}
Проблема решена!