异步下载图像到NSData和缓存

时间:2013-03-01 21:03:21

标签: ios objective-c nsdata grand-central-dispatch

首先,这不是一个重复的问题。我已经阅读了很多关于Stack Overflow的问题,但它们并没有帮助我完全解决我的问题。

我正在从网络服务下载图片。由于没有人喜欢停止使用UI,我使用线程分别下载图像。

NSURL *imageUrl = [NSURL URLWithString:storyImageURL];

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{

    thumbnailData = [NSData dataWithContentsOfURL:imageUrl];

    dispatch_async(dispatch_get_main_queue(), ^{
        thumbnail = [UIImage imageWithData:thumbnailData];
    });
});

如果我使用与上面完全相同的代码,UI将不会停止,直到它从Web服务获取数据,但图像不会被缓存。

如果我不使用线程,那么UI将停止,但是使用NSCoding方法(存档)缓存图像。

我的问题是:我可以做什么来同时使用线程和缓存缩略图?请不要建议任何第三方库。

更新:一次又一次地浏览代码后,我可以想到两个问题:

1)看起来NSKeyedArchiver和NSKeyedUnarchiver在线程完成下载图像之前被调用,但这只是猜测。在一个单独的商店文件中,我正在使用NSKeyedArchiver和NSKeyedUnarchiver:

- (RSSChannel *)fetchRSSFeedWithCompletion:(void (^)(RSSChannel *, NSError *))block
{
    NSURL *url = [NSURL URLWithString:@"http://techcrunch.com/feed"];
    NSURLRequest *req = [NSURLRequest requestWithURL:url];

    RSSChannel *channel = [[RSSChannel alloc] init];
    TheConnection *connection = [[TheConnection alloc] initWithRequest:req];

    //[connection setCompletionBlock:block];

    NSString *cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
    cachePath = [cachePath stringByAppendingPathComponent:@"HAHAHA.archive"];

    RSSChannel *cachedChannel = [NSKeyedUnarchiver unarchiveObjectWithFile:cachePath];

    if (!cachedChannel) 
        cachedChannel = [[RSSChannel alloc] init];

    RSSChannel *channelCopy = [cachedChannel copy];

    [connection setCompletionBlock:^(RSSChannel *obj, NSError *err) {
        if (!err) {

            [channelCopy addItemsFromChannel:obj];
            [NSKeyedArchiver archiveRootObject:channelCopy toFile:cachePath];
        }
        block(channelCopy, err);
    }];


    [connection setXmlRootObject:channel];
    [connection start];

    return cachedChannel;
}

2)我能想到的第二个问题是,在尝试从缓存中解码缩略图后,UI并没有刷新。

6 个答案:

答案 0 :(得分:3)

  

1)看起来NSKeyedArchiver和NSKeyedUnarchiver在线程完成下载图像之前被调用,但这只是猜测。在一个单独的商店文件中,我正在使用NSKeyedArchiver和NSKeyedUnarchiver:

你在这里走在正确的轨道上。

您需要RSSChannel与远程获取数据的后台任务之间的同步机制,以便仅在下载完所有图像后调用archiveRootObject

处理此问题的一种方法是使用dispatch_group处理所有图像下载。然后,您可以在执行archiveRootObject之前使完成块等待该调度组。我不久前写了一篇关于这个的要点,我认为它也应该对你有所帮助:https://gist.github.com/sdesimone/4579906。如果没有,请报告具体内容。 (可能你需要修复一些编译错误。)

另一种解决方法是管理共享计数器:你

  1. 在Feed解析开始时递增计数器,并在完成块中递减计数器:

    RSSChannel *channelCopy = [cachedChannel copy];
    
    INCREMENT_COUNTER
    

    [connection setCompletionBlock:^(RSSChannel * obj,NSError * err){      if(!err){

        [channelCopy addItemsFromChannel:obj];
        DECREMENT_COUNTER;
     }
    block(channelCopy, err);
    }];
    
  2. 每次找到要下载的图像时递增计数器,然后在图像下载完毕后递减计数器;当计数器达到零时,您知道可以存档:

    NSURL *imageUrl = [NSURL URLWithString:storyImageURL];
    
    INCREMENT_COUNTER;
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
    thumbnailData = [NSData dataWithContentsOfURL:imageUrl];
    
    dispatch_async(dispatch_get_main_queue(), ^{
      thumbnail = [UIImage imageWithData:thumbnailData];
      DECREMENT_COUNTER;
      if (COUNTER_REACHED_ZERO)
        CALL_ARCHIVE_METHOD_ON_CHANNEL OBJECT
      });
    });
    
  3. 这需要进行一些重构:您需要将频道存储为属性(因此您可以在原始方法之外使用它(请参阅第1点)。

    我告诉你如何实施共享计数器;只关注使其实现线程安全!

    希望这会有所帮助。

答案 1 :(得分:0)

您是否将thumbnailData保存为实例变量?在发送之前检查是否设置了实例变量,如果是,则返回该值。如果没有,则运行您的调度块并将其保存为实例变量。

答案 2 :(得分:0)

使用此: http://www.markj.net/hjcache-iphone-image-cache/

它完成了你想要的一切。

答案 3 :(得分:0)

假设您的目标是iOS 5+,最自然的解决方案是根据需要使用NSURLCacheNSURLConnection +sendAsynchronousRequest:queue:completionHandler:。第三方解决方案通常会忽略这些方法,无论是通过无知还是希望支持iOS 4,因此您可以选择有效地将这些内容维护到Apple,信任第三方或花费您自己的时间。< / p>

E.g。

NSURLRequest *request =
   [NSMutableURLRequest requestWithURL:imageURL];

// just use the shared cache unless you have special requirements
NSURLCache *cache = [NSURLCache sharedURLCache]; 

NSCachedURLResponse *response = [cache cachedResponseForRequest:request];

// we'll just lazily assume that if anything is in the
// cache then it will do
if(response)
{
    [self proceedWithData:response.data];
}
else
{
    // fetch the data
    [NSURLConnection
        sendAsynchronousRequest:request
        queue:[NSOperationQueue mainQueue] // this dictates where the completion
                                           // handler is called; it doesn't make
                                           // the fetch block the main queue
        completionHandler:
            ^(NSURLResponse *response, NSData *data, NSError *error)
            {
                 // TODO: error handling here

                 [cache
                     storeCachedResponse:
                         [[NSCachedURLResponse alloc]
                             initWithResponse:response dat:data]
                     forRequest:request];

                  [self proceedWithData:data];
            }];
}

NSURLCache存在于iOS 5之前,但仅限于内存缓存。从5开始它也是一个磁盘缓存。

答案 4 :(得分:0)

SDWebImage是图像缓存的最佳库。

答案 5 :(得分:0)

试试这个

    NSURL *imageUrl = [NSURL URLWithString:storyImageURL];

    UIButton *btnThumbnail = [[UIButton alloc] initWithFrame:CGRectMake(0, 10, 180, 280)];
    [self downloadingServerImageFromUrl:btnThumbnail AndUrl:imageUrl];
    [btnThumbnail addTarget:self action:@selector(onSelectEPaper:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:viewPaperBg];

 - (void)onSelectEPaper:(id)sender
  {
  }


  -(void)downloadingServerImageFromUrl:(UIButton*)imgView AndUrl:(NSString*)strUrl
{
//    strUrl = [strUrl encodeUrl];
//    strUrl = [strUrl stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];

NSString* theFileName = [NSString stringWithFormat:@"%@.jpg",[[strUrl lastPathComponent] stringByDeletingPathExtension]];


NSFileManager *fileManager =[NSFileManager defaultManager];
NSString *fileName = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"tmp/%@",theFileName]];


imgView.backgroundColor = [UIColor darkGrayColor];
UIActivityIndicatorView *actView = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
[imgView addSubview:actView];
[actView startAnimating];
CGSize boundsSize = imgView.bounds.size;
CGRect frameToCenter = actView.frame;
// center horizontally
if (frameToCenter.size.width < boundsSize.width)
    frameToCenter.origin.x = (boundsSize.width - frameToCenter.size.width) / 2;
else
    frameToCenter.origin.x = 0;

// center vertically
if (frameToCenter.size.height < boundsSize.height)
    frameToCenter.origin.y = (boundsSize.height - frameToCenter.size.height) / 2;
else
    frameToCenter.origin.y = 0;

actView.frame = frameToCenter;


dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_async(queue, ^{

    NSData *dataFromFile = nil;
    NSData *dataFromUrl = nil;

    dataFromFile = [fileManager contentsAtPath:fileName];
    //      NSLog(@"%@",fileName);
    if(dataFromFile==nil){
        //      NSLog(@"%@",strUrl);
        NSString *url =[strUrl stringByReplacingOccurrencesOfString:@"\n" withString:@""];
        url=[url stringByReplacingOccurrencesOfString:@"\t" withString:@""];
        url=[url stringByReplacingOccurrencesOfString:@" " withString:@""];

        url = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
        //       dataFromUrl=[[[NSData alloc] initWithContentsOfURL:[NSURL URLWithString:url]] autorelease];
        //       dataFromUrl=[[NSData dataWithContentsOfURL:[NSURL URLWithString:url]] autorelease];
        NSError* error = nil;
        //     NSLog(@"%@", [NSURL URLWithString:url]);
        dataFromUrl = [NSData dataWithContentsOfURL:[NSURL URLWithString:url] options:NSDataReadingUncached error:&error];

        if (error) {
            NSLog(@"%@", [error localizedDescription]);
        } else {
            //    NSLog(@"Data has loaded successfully.");
        }
    }

    dispatch_sync(dispatch_get_main_queue(), ^{

        if(dataFromFile!=nil){
            //    imgView.image = [UIImage imageWithData:dataFromFile];
            [imgView setBackgroundImage:[UIImage imageWithData:dataFromFile] forState:UIControlStateNormal];
        }else if(dataFromUrl!=nil){
            //    imgView.image = [UIImage imageWithData:dataFromUrl];
            [imgView setBackgroundImage:[UIImage imageWithData:dataFromUrl] forState:UIControlStateNormal];
            NSString *fileName = [NSHomeDirectory() stringByAppendingPathComponent:[NSString stringWithFormat:@"tmp/%@",theFileName]];

            BOOL filecreationSuccess = [fileManager createFileAtPath:fileName contents:dataFromUrl attributes:nil];
            if(filecreationSuccess == NO){
                //    NSLog(@"Failed to create the html file");
            }

        }else{
            //     imgView.image = [UIImage imageNamed:@"no_image.jpg"];
            [imgView setBackgroundImage:[UIImage imageNamed:@"no_image.jpg"] forState:UIControlStateNormal];
        }
        [actView removeFromSuperview];
        //            [actView release];
        //    [imgView setBackgroundColor:[UIColor clearColor]];
    });
});
}