5

我碰上了众所周知的墙,试图弄清楚如何使用从我的桌面应用程序(不是 iPhone 应用程序!!)中的异步 NSURLConnection 返回的数据填充 NSImage。

这是情况。

我有一个使用自定义单元格的表格。在每个自定义单元格中都有一个 NSImage,它是从 Web 服务器中提取的。为了填充图像,我可以轻松地执行同步请求:

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];
}

我希望这是一个可以解决的简单问题,但我担心我在这方面的知识有限,尽管进行了一些严肃的谷歌搜索并尝试了许多不同的推荐方法,但我仍在努力想出一个解决方案。

最终,我将为我的应用程序拥有一个复杂的缓存机制,其中表格视图会在输出图像并将它们从服务器获取之前检查本地计算机,并且可能在检索图像之前有一个进度指示器。现在,如果使用同步过程的图像足够大,即使是本地图像填充也会变得迟缓。

任何和所有的帮助将不胜感激。

解决方案更新

万一其他人需要类似的解决方案,这要感谢 Ben 的帮助,这就是我想出的(当然,为了发布而进行了一般修改)。请记住,我还实现了图像的自定义缓存,并使我的图像加载类足够通用,以供我的应用程序中的各个地方用于调用图像。

在我的调用方法中,在我的情况下是表格中的自定义单元格......

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,一个唯一的引用,我用来确定在设置通知观察者时哪个类进行了调用,图像的 ID,我是否要将图像保存到缓存以及路径到缓存。

我的 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;
}

问题解决了!

4

3 回答 3

3

我的解决方案是为此目的使用 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),因此没有保留和释放。)

于 2011-11-08T21:08:41.863 回答
1

你的直觉是正确的;您希望从作为 NSURLConnection 的委托的对象回调到管理表视图的控制器,该控制器将更新您的数据源,然后-setNeedsDisplayInRect: 使用图像对应的行的矩形进行调用。

于 2009-11-16T18:51:16.383 回答
0

您是否尝试过使用该initWithContentsOfURL:方法?

于 2009-11-16T05:27:35.043 回答