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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1740209/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-14 18:57:36  来源:igfitidea点击:

Populating NSImage with data from an asynchronous NSURLConnection

objective-ccocoaxcodeasynchronousnsurlconnection

提问by Hooligancat

I have hit the proverbial wall trying to figure out how to populate an NSImage with data returned from an asynchronous NSURLConnection in my desktop app (NOT an iPhone application!!).

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

Here is the situation.

这是情况。

I have a table that is using custom cells. In each custom cell is an NSImage which is being pulled from a web server. In order to populate the image I can do a synchronous request easily:

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

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

The problem with this is that the table blocks until the images are populated (obviously because it's a synchronous request). On a big table this makes scrolling unbearable, but even just populating the images on the first run can be tedious if they are of any significant size.

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

So I create an asynchronous request class that will retrieve the data in its own thread as per Apple's documentation. No problem there. I can see the data being pulled and populated (via my log files).

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

The problem I have is once I have the data, I need a callback into my calling class (the custom table view).

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

I was under the impression that I could do something like this, but it doesn't work because (I'm assuming) that what my calling class really needs is a delegate:

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

NSImage * myIMage;
myImage = [myConnectionClass getMyImageMethod];

In my connection class delegate I can see I get the data, I just don't see how to pass it back to the calling class. My connectionDidFinishLoadingmethod is straight from the Apple docs:

在我的连接类委托中,我可以看到我获得了数据,只是不知道如何将其传递回调用类。我的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];
}

I am hoping this is a simple problem to solve, but I fear I am at the limit of my knowledge on this one and despite some serious Google searches and trying many different recommended approaches I am struggling to come up with a solution.

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

Eventually I will have a sophisticated caching mechanism for my app in which the table view checks the local machine for the images before going out and getting them form the server and maybe has a progress indicator until the images are retrieved. Right now even local image population can be sluggish if the image's are large enough using a synchronous process.

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

Any and all help would be very much appreciated.

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

Solution Update

解决方案更新

In case anyone else needs a similar solution thanks to Ben's help here is what I came up with (generically modified for posting of course). Bear in mind that I have also implemented a custom caching of images and have made my image loading class generic enough to be used by various places in my app for calling images.

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

In my calling method, which in my case was a custom cell within a table...

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

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

This creates an instance of myLoader class and passes it 4 parameters. The URL of the image I want to get, a unique reference that I use to determine which class made the call when setting up the notification observers, the ID of the image, whether I want to save the image to cache or not and the path to the cache.

这将创建一个 myLoader 类的实例并向它传递 4 个参数。我想要获取的图像的 URL,我用来确定在设置通知观察者时调用哪个类的唯一引用,图像的 ID,是否要将图像保存到缓存以及路径到缓存。

My ImageLoaderClass defines the method called above where I set what is passed from the calling cell:

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

In the connectionDidFinishLoadingmethod I saved the file to cache if needed and made a notification call to any listening observers:

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

Finally in the table controller set up an observer to listen for the notification and send it off to the method to handle re-displaying the custom cell:

最后在表控制器中设置一个观察者来监听通知并将其发送到处理重新显示自定义单元格的方法:

-(id)init
{
    [super init];

    NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];

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

    return self;
}

Problem solved!

问题解决了!

采纳答案by Ben Stiglitz

Your intuition is correct; you want to have a callback from the object which is the NSURLConnection's delegate to the controller which manages the table view, which would update your data source and then call -setNeedsDisplayInRect:with the rect of the row to which the image corresponds.

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

回答by Jeena

My solution is to use Grand Central Dispatch (GCD) for this purpose, you could save the image to disc too in the line after you got it from the server.

我的解决方案是为此使用 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;
}

(I am using Automatic Reference Counting (ARC) therefore there are no retain and release.)

(我正在使用自动引用计数 (ARC),因此没有保留和释放。)

回答by Marco Mustapic

Have you tried using the initWithContentsOfURL:method?

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