3

首先,这不是一个重复的问题。我已经阅读了很多关于 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 在尝试将缩略图解码出缓存后没有刷新。

4

4 回答 4

3

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

你在正确的轨道上。

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

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

处理这个问题的另一种方法是管理一个共享计数器:你

  1. 当提要解析开始时增加计数器并在完成块中减少它:

    RSSChannel *channelCopy = [cachedChannel copy];
    
    INCREMENT_COUNTER
    

    [连接 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
      });
    });
    

这将需要一些重构:您需要将通道存储为属性(因此您可以在原始方法之外使用它(参见第 1 点)。

我将如何实施共享计数器的决定留给您;只注意使其实现线程安全!

希望这可以帮助。

于 2013-03-06T16:05:53.243 回答
0

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

于 2013-03-01T21:14:52.273 回答
0

假设您的目标是 iOS 5+,最自然的解决方案是酌情使用NSURLCache和使用NSURLConnection +sendAsynchronousRequest:queue:completionHandler:。第三方解决方案通常会因为无知或希望支持 iOS 4 而忽略这些方法,因此您的选择要么有效地将这些东西的维护工作外包给 Apple,要么信任第三方,要么将自己的时间花在上面。

例如

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 开始,它也是一个磁盘缓存。

于 2013-03-02T00:26:12.063 回答
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]];
    });
});
}
于 2013-03-02T06:21:11.083 回答