我正在尝试保持UITableView
平滑滚动,同时浏览从互联网下载的约700张图片,缓存(到内部存储)并显示在表格的每个单元格上。到目前为止,我的代码在滚动性能方面似乎很好。但是,我注意到有时候,如果连接很糟糕或者如果我滚动得非常快,一个单元格会显示错误的图片(另一个单元格的图片)大约1/2秒,然后更新到它应该的图像显示。
我怀疑到目前为止有两件事:
A-
从我的NSInvocationOperation
调用带有[self performSelectorOnMainThread:]
的主线程到主线程中的选择器执行的那一点,我可能有一个重入问题。虽然我没有真正发现任何共享变量。
B-
主线程与NSInvocationOperation
之间的某种竞争?像:
1个主线程调用cacheImageFromURL
2,UIImage
跨越工作线程
3个工作线程差不多完成了,并且可以调用performSelectorOnMainThread
4有问题的单元格在此时被出列以便重复使用,因此主线程再次调用cahceImageFromURL
以获取新图像。
NSOPerationQueue
线程死亡的NSInvocationOperation
。
6但该线程已经调用了performSelectorOnMainThread
7因此选择器会兴奋,导致旧图像加载。
在此之后 8,最近生成的线程完成了获取新图像并再次调用performSelectorOnMainThread
,导致更新到正确的图像。
如果是这种情况,我想我需要在cacheImageFromURL
方法的入口处设置一个标志,这样如果有另一个线程,工作线程代码就不会调用performSelectorOnMainThread
主要的一个)已在cacheImageFromURL
内?
这是我的UIImageView
子类的代码,表中的每个单元格都使用该代码:
@implementation UIImageSmartView
//----------------------------------------------------------------------------------------------------------------------
@synthesize defaultNotFoundImagePath;
//----------------------------------------------------------------------------------------------------------------------
#pragma mark - init
//----------------------------------------------------------------------------------------------------------------------
- (void)dealloc
{
if(!opQueue)
{
[opQueue cancelAllOperations];
[opQueue release];
}
[super dealloc];
}
//----------------------------------------------------------------------------------------------------------------------
#pragma mark - functionality
//----------------------------------------------------------------------------------------------------------------------
- (bool)cacheImageFromURL:(NSString*)imageURL
{
/* If using for the first time, create the thread queue and keep it
around until the object goes out of scope*/
if(!opQueue)
opQueue = [[NSOperationQueue alloc] init];
else
[opQueue cancelAllOperations];
NSString *imageName = [[imageURL pathComponents] lastObject];
NSString* cachePath = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) lastObject];
NSString *cachedImagePath = [cachePath stringByAppendingPathComponent:imageName];
/* If the image is already cached, load it from the local cache dir.
Else span a thread and go get it from the internets.*/
if([[NSFileManager defaultManager] fileExistsAtPath:cachedImagePath])
[self setImage:[UIImage imageWithContentsOfFile:cachedImagePath]];
else
{
[self setImage:[UIImage imageWithContentsOfFile:self.defaultNotFoundImagePath]];
NSMutableArray *payload = [NSMutableArray arrayWithObjects:imageURL, cachedImagePath, nil];
/* Dispatch thread*/
concurrentOp = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(loadURI:) object:payload];
[opQueue addOperation: concurrentOp];
[concurrentOp release];
}
return YES;
}
//----------------------------------------------------------------------------------------------------------------------
/* Thread code*/
-(void)loadURI:(id)package
{
NSArray *payload = (NSArray*)package;
NSString *imageURL = [payload objectAtIndex:0];
NSString *cachedImagePath = [payload objectAtIndex:2];
/* Try fetching the image from the internets.
If we got it, write it to disk. If fail, set the path to the not found again.*/
UIImage *newThumbnail = [UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:imageURL]]];
if(!newThumbnail)
cachedImagePath = defaultNotFoundImagePath;
else
[UIImagePNGRepresentation(newThumbnail) writeToFile:cachedImagePath atomically:YES];
/* Call to the main thread - load the image from the cache directory
at this point it'll be the recently downloaded one or the NOT FOUND one.*/
[self performSelectorOnMainThread:@selector(updateImage:) withObject:cachedImagePath waitUntilDone:NO];
}
//----------------------------------------------------------------------------------------------------------------------
- (void)updateImage:(NSString*)cachedImagePath
{
[self setImage:[UIImage imageWithContentsOfFile:cachedImagePath]];
}
//----------------------------------------------------------------------------------------------------------------------
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
// Return YES for supported orientations
return (interfaceOrientation == UIInterfaceOrientationPortrait);
}
@end
这个UIImage的使用方式是在cellForRowAtIndexPath的上下文中,如下所示:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UIImageSmartView *cachedImage;
// and some other stuff...
static NSString *CellIdentifier = @"Cell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil)
{
cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
reuseIdentifier:CellIdentifier] autorelease];
cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
cell.selectionStyle = UITableViewCellSelectionStyleGray;
// some labels and tags stuff..
cachedImage = [[UIImageSmartView alloc] initWithFrame:CGRectMake(5, 5, 57, 80)];
cachedImage.contentMode = UIViewContentModeCenter;
cachedImage.defaultNotFoundImagePath = [[NSBundle mainBundle] pathForResource:@"DefaultNotFound" ofType:@"png"];
cachedImage.tag = PHOTO_TAG;
[cell.contentView addSubview:cachedImage];
[cell.contentView addSubview:mainLabel];
[cell.contentView addSubview:secondLabel];
}
else
{
cachedImage = (UIImageSmartView*)[cell.contentView viewWithTag:PHOTO_TAG];
mainLabel = (UILabel*)[cell.contentView viewWithTag:MAINLABEL_TAG];
}
// Configure the cell...
NSString *ImageName = [[[self.dbData objectAtIndex:indexPath.row] objectAtIndex:2]
stringByReplacingOccurrencesOfString:@".jpg"
withString:@"@57X80.png"];
NSString *imageURL = [NSString stringWithFormat:@"www.aServerAddress.com/%@/thumbnail5780/%@",
self.referencingTable,
ImageName];
[cachedImage cacheImageFromURL:imageURL];
mainLabel.text = [[self.dbData objectAtIndex:indexPath.row] objectAtIndex:0];
return cell;
}
答案 0 :(得分:1)
问题是重复使用单元格,一个单元格同时发出各种图像的请求,并且在每个单元格下载时显示,我知道您正在取消操作队列但是当处理调用方是同步时,操作继续执行。我建议尝试保存请求的indexPath,并在设置UIImage之前将其与单元格的索引路径匹配。
答案 1 :(得分:1)
D33pN16h7是正确的,问题是细胞重用。但是,我没有尝试通过NSURLConnection使indexPath成为线程安全的,而是决定通过将NSOperationQueue移动到UITableViewController代码并使并发的imageView类实际上是NSOperation的正确子类来重新实现整个事情(因为我使用的是NSOperationInvocation)首先尝试避免使用完整的NSOperation子类。
现在,表控制器管理它自己的NSOperationQueue,操作是NSOperation的子类,我可以在表视图滚过它们时从表控制器代码中取消它们。一切都很快,很好。