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

时间:2009-11-16 05:19:38

标签: objective-c cocoa xcode asynchronous nsurlconnection

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

情况就是这样。

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

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

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

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

非常感谢任何和所有帮助。

解决方案更新

如果其他人需要类似的解决方案,感谢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;
}

问题解决了!

3 个答案:

答案 0 :(得分: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),因此没有保留和释放。)

答案 1 :(得分:1)

你的直觉是正确的;你希望从对象获得一个回调,这是NSURLConnection对管理表视图的控制器的委托,它将更新你的数据源,然后用图像对应的行的矩形调用-setNeedsDisplayInRect:。 / p>

答案 2 :(得分:0)

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