异步负载的最佳实践可能是相同的地址

时间:2014-08-17 20:37:51

标签: objective-c uitableview caching asynchronous uiimage

我从亚马逊的Web服务下载图像以获取UITableView。我还使用单例来缓存最近的图像。我想知道如何最好地处理一种情况,例如,我在视图中有两个单元格,并且两个实际上都使用相同的键(如果使用NSURLSession,URL将是相同的情况)。显然不需要两次下载图像,但检查缓存不会返回结果,因为第二次下载开始时第一次下载将不会完成。我目前的逻辑是使用一个额外的NSDictionary单例,它将包含当前正在下载但不完整的每个图像的密钥。检查缓存后我会检查一下,如果密钥存在,我会将UIImageView的引用添加到密钥的数组中 - 当这个下载完成后,这个数组就可以用来更新所有的UIImageViews在数组中引用。这不完全是一个"清洁"在我看来这样做的方法 - 是否有更好的处理方法?虽然这个问题更具概念,但这是我目前的代码:

custom_image_view.m

-(void)set_image:(NSString *)key desired_size:(CGSize)desired_size
{
singleton *caches = [singleton instance];
if (!key.length || [key isEqualToString:@"0"])
{
    return;
}
if (![caches is_file_cached:key type:IMAGE_CACHE])
{
//should i check a new dictionary here to see if it's currently downloading?
//if downloading, add self to array and return
//if not downloading, make new key
    NSString *bucket = @"bucket.mysite.com";

    NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
    NSString *documents_directory = [paths objectAtIndex:0];
    NSString *image_path =[documents_directory stringByAppendingPathComponent:[NSString stringWithFormat:@"%@.jpg",@"cached"]];

//only relevant for AWS users
    AWSStaticCredentialsProvider *credentialsProvider = [AWSStaticCredentialsProvider credentialsWithAccessKey:@"accesskey" secretKey:@"secretkey"];
    AWSServiceConfiguration *configuration = [AWSServiceConfiguration configurationWithRegion:AWSRegionUSEast1 credentialsProvider:credentialsProvider];
    [AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration;

    download_request = [AWSS3GetObjectRequest new];
    download_request.bucket = bucket;
    download_request.key = key;
    download_request.downloadingFileURL = [NSURL fileURLWithPath:image_path];
    AWSS3 *transferManager = [[AWSS3 alloc] initWithConfiguration:configuration];
    NSLog(@"manager: %@",transferManager);
//begin request
    [[transferManager getObject:download_request] continueWithBlock:^id(BFTask *task)
     {
         if (task.error != nil)
         {
             NSLog(@"error");
             if(task.error.code != AWSS3TransferManagerErrorCancelled && task.error.code != AWSS3TransferManagerErrorPaused)
             {
                 NSLog(@"error code: %@",task.error);
                 NSLog(@"key: %@",key);
             }
         }
         else
         {
             //successful download
             self->download_request = nil;
             dispatch_async(dispatch_get_main_queue(), ^
                            {
                                NSData *data = [NSData dataWithContentsOfFile:image_path];
                                UIImage *image = [UIImage imageWithData:data];
                                if (image != nil)
                                {
                                    self.image = [self crop_image:image to_size:desired_size];
     //loop through new dictionary's array and assign image to each as well?
                                    [caches add_file_to_cache:key withData:data type:IMAGE_CACHE];
                                }
                            });
         }
         return nil;
     }];
}
else
{
    dispatch_async(dispatch_get_main_queue(), ^
                   {
                       NSData *data = [caches get_cached_data:key type:IMAGE_CACHE];
                       UIImage *image = [UIImage imageWithData:data];
                       self.image = [self crop_image:image to_size:desired_size];
                   });
}
}

2 个答案:

答案 0 :(得分:1)

BoltsFramework让你的这项任务变得更轻松,但你仍然需要一些缓存。

getObject:返回BFTaskcontinueWithBlock:BFTask上的一个方便的方法,它将在任务完成后执行一个块。它也已经能够通过相同的任务完成属性来执行多个块,从而为您节省了大量的工作。剩下的就是将整个事物拉到一起只是从image_path到BFTask的简单缓存。这里有一些代码可以更好地解释。

某处:

static NSMutableDictionary * _imagePathToTask = [NSMutableDictionary new];

而不是:

[[transferManager getObject:download_request] continueWithBlock:^id(BFTask *task) {

做这样的事情:

BFTask *task = [_imagePathToTask objectForKey:image_path];
if (task == nil) {
    task = [transferManager getObject:download_request];
    [_imagePathToTask setObject:task forKey:image_path];
}
[task continueWithBlock:^id(BFTask *task) {

即使任务完成,也会使用该任务调用您的块。因此,只要您的词典存在,您的图像请求都可以被发送,因为您知道您不会两次下载相同的图像。

现在唯一最重要的是粗略的是我们如何存储字典。更好的方法可能是为AWSS3创建一个类别,为您返回文件路径的BFTask。这样做会使您的实现回到UIImageView类别中相同数量的代码。您可以使用associated object在AWSS3实例上存储image_path->任务字典。我想你无论如何都可能将其中一个包装起来重用,但也许你从代码中省略了它以保持你的例子简短。

编辑: 但我想更直接地回答你的问题,你有正确的想法。如果您查看BFTask和回调NSMutableArray,他们就会按照您的描述进行操作。最大的不同是应用程序中的级别。

答案 1 :(得分:0)

使用GitHub上提供的SDWebImage访问https://github.com/rs/SDWebImage。 通过它,我在我的项目中使用它在相同的情况下它有用。 该库为UIImageView提供了一个类别,支持来自Web的远程图像。 它基本上是异步映像下载器,具有UIImageView类别的缓存支持。