使用来自异步 NSURLConnection 的数据填充 NSImage

Posted

技术标签:

【中文标题】使用来自异步 NSURLConnection 的数据填充 NSImage【英文标题】:Populating NSImage with data from an asynchronous NSURLConnection 【发布时间】:2009-11-16 05:19:38 【问题描述】:

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

情况如下。

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

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

这样做的问题是表格会阻塞,直到图像被填充(显然是因为它是一个同步请求)。在一张大桌子上,这让滚动变得难以忍受,但如果它们的尺寸太大,即使只是在第一次运行时填充图像也会很乏味。

所以我创建了一个异步请求类,它将按照Apple's documentation 在自己的线程中检索数据。那里没问题。我可以看到正在提取和填充的数据(通过我的日志文件)。

我遇到的问题是,一旦我有了数据,我需要回调到我的调用类(自定义表格视图)。

我的印象是我可以做这样的事情,但它不起作用,因为(我假设)我的调用类真正需要的是委托:

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 搜索并尝试了许多不同的推荐方法,但我仍在努力想出一个解决方案。

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

我们将不胜感激。

解决方案更新

如果有人在 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;

问题解决了!

【问题讨论】:

【参考方案1】:

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

【讨论】:

【参考方案2】:

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

【讨论】:

本,感谢您的回复。你会推荐一种在我的调用类中添加观察者并从 connectionDidFinishLoading 进程发送通知并以这种方式传递对象的方法吗? 那肯定行得通;只需传递包含图像及其对应资源的字典或其他结构。如果您选择问题中提到的本地缓存方法,则可以让连接直接与之对话。 本,感谢您的帮助。工作了一个款待。我为可能遇到类似问题的其他人更新了上面的解决方案。【参考方案3】:

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

【讨论】:

Marco 我试过了,但它也是一个阻塞调用。

以上是关于使用来自异步 NSURLConnection 的数据填充 NSImage的主要内容,如果未能解决你的问题,请参考以下文章

带有异步NSURLConnection调用的MBProgressHUD

Swift3.0:NSURLConnection的使用

iOS网络开发使用NSURLConnection

NSURLSession与NSURLConnection区别

网络请求

iPhone:异步 HEAD 方法