首先,这不是一个重复的问题。我已经阅读了很多关于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并没有刷新。
答案 0 :(得分:3)
1)看起来NSKeyedArchiver和NSKeyedUnarchiver在线程完成下载图像之前被调用,但这只是猜测。在一个单独的商店文件中,我正在使用NSKeyedArchiver和NSKeyedUnarchiver:
你在这里走在正确的轨道上。
您需要RSSChannel与远程获取数据的后台任务之间的同步机制,以便仅在下载完所有图像后调用archiveRootObject
。
处理此问题的一种方法是使用dispatch_group处理所有图像下载。然后,您可以在执行archiveRootObject
之前使完成块等待该调度组。我不久前写了一篇关于这个的要点,我认为它也应该对你有所帮助:https://gist.github.com/sdesimone/4579906。如果没有,请报告具体内容。 (可能你需要修复一些编译错误。)
另一种解决方法是管理共享计数器:你
在Feed解析开始时递增计数器,并在完成块中递减计数器:
RSSChannel *channelCopy = [cachedChannel copy];
INCREMENT_COUNTER
[connection setCompletionBlock:^(RSSChannel * obj,NSError * err){ if(!err){
[channelCopy addItemsFromChannel:obj];
DECREMENT_COUNTER;
}
block(channelCopy, err);
}];
每次找到要下载的图像时递增计数器,然后在图像下载完毕后递减计数器;当计数器达到零时,您知道可以存档:
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点)。
我告诉你如何实施共享计数器;只关注使其实现线程安全!
希望这会有所帮助。
答案 1 :(得分:0)
您是否将thumbnailData
保存为实例变量?在发送之前检查是否设置了实例变量,如果是,则返回该值。如果没有,则运行您的调度块并将其保存为实例变量。
答案 2 :(得分:0)
使用此: http://www.markj.net/hjcache-iphone-image-cache/
它完成了你想要的一切。
答案 3 :(得分:0)
假设您的目标是iOS 5+,最自然的解决方案是根据需要使用NSURLCache
和NSURLConnection +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]];
});
});
}