iOS:如何阻止后台线程更新主线程中的用户界面?

我实现了一个UIImageView单元的UITableView,每个单元通过NSTimer每隔5秒定时刷新一次。 每个图像从后台线程的服务器加载,并从该后台线程,我也更新UI,显示新的图像,通过调用performSelectorOnMainThread 。 到现在为止还挺好。

目前,当我不等到一个图像被加载到当前单元格时,我会面临一个问题,并快速向下滚动到一个新的单元格。 然而,新的单元格显示的是前一个单元格的图像,而不是新单元格的图像。 虽然下一轮的NSTimer将会正确地显示图像,但是这可能首先会让用户感到困惑。

如果我不重用UITableView的单元格,问题就会消失,但是如果在我的应用程序中显示单元格的数量,这不是一个选项。

所以我能想到的唯一的解决scheme是取消(或杀死)将要显示旧图像的后台线程,如果我知道用户执行滚动操作。

我想知道这可能不是最好的做法,因此,寻求你的意见。

(我也不能使用SDWebImage,因为我的要求是显示从服务器加载的循环中的一组图像)

 // In MyViewController.m - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { ... NSTimer* timer=[NSTimer scheduledTimerWithTimeInterval:ANIMATION_SCHEDULED_AT_TIME_INTERVAL target:self selector:@selector(updateImageInBackground:) userInfo:cell.imageView repeats:YES]; ... } - (void) updateImageInBackground:(NSTimer*)aTimer { [self performSelectorInBackground:@selector(updateImage:) withObject:[aTimer userInfo]]; } - (void) updateImage:(AnimatedImageView*)animatedImageView { @autoreleasepool { [animatedImageView refresh]; } } // In AnimatedImageView.m -(void)refresh { if(self.currentIndex>=self.urls.count) self.currentIndex=0; ASIHTTPRequest *request=[[ASIHTTPRequest alloc] initWithURL:[self.urls objectAtIndex:self.currentIndex]]; [request startSynchronous]; UIImage *image = [UIImage imageWithData:[request responseData]]; // How do I cancel this operation if I know that a user performs a scrolling action, therefore departing from this cell. [self performSelectorOnMainThread:@selector(performTransition:) withObject:image waitUntilDone:YES]; } -(void)performTransition:(UIImage*)anImage { [UIView transitionWithView:self duration:1.0 options:(UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction) animations:^{ self.image=anImage; currentIndex++; } completion:^(BOOL finished) { }]; } 

好的…另一种方法是将你的'图像请求代码'放入你的AnimatedImageView,并且每当你设置一个新的图像URL

 // your controller - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { ... [cell.imageView setDistantImage:imageURL]; ... } 

然后,在你的Cell类中,“setDistantImage”创build一个ASIHttpRequest,并使前一个

 //your ImageView class @property (...) ASIHttpRequest *distantImageRequest; - (void) setDistantImageUrl:(NSUrl *)imageUrl { //clear previous request [distantImageRequest clearDelegatesAndCancel]; //set a new request, with the callBack embedded directly in this ImageView class ... } //animation and callBacks methods in this ImageView class too... 

当在CollectionView和TableView中加载许多图像时,使用重复使用的单元格快速和长时间滚动覆盖所有下载的图像时,我遇到了完全相同的问题。

对于TableView,我解决了这个问题,在WWDC2012中提供了“didEndDisplayingCell”方法,但对于没有这种方法的CollectionView来说,这并不是有用的。 :O(

最后,我find了一个解决scheme…幸运的是。 这个想法是创build一个由TableView或CollectionView中的单元格调用的ImageLoader类,它将负责下载图像 。 在你的实现中,添加这个片段来隐藏variables到世界其他地方:

 @interface ImageLoader() { __block NSURLSession *_session; } 

然后,在你的实现中添加下面的函数:

 - (void)getViewForCell:(UIImageView*)cell FromURL:(NSURL*)url { NSBlockOperation *_loadImageOp; __block NSOperationQueue *_opQueue; _opQueue = [[NSOperationQueue alloc]init]; if (_session != nil) { [_session invalidateAndCancel]; } _loadImageOp = [[NSBlockOperation alloc]init]; [_loadImageOp addExecutionBlock:^{ NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration]; _session = [NSURLSession sessionWithConfiguration:config delegate:nil delegateQueue:_opQueue]; NSURLSessionDownloadTask *downloadTask = [_session downloadTaskWithURL:url completionHandler:^(NSURL *location, NSURLResponse *response, NSError *error) { UIImage *imageCell = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]]; if (imageCell) { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ cell.image = imageCell; }]; } else { [[NSOperationQueue mainQueue] addOperationWithBlock:^{ cell.image = [UIImage imageNamed:@"imageNotWorking.png"]; }]; } }]; [downloadTask resume]; }]; [_opQueue addOperation:_loadImageOp]; } 

这个想法是1 /。 你创build一个块,其中成员variables_session将通过downloadTask,2 /获得传入参数中给定的url。 下载时,通过mainQueue将图像显示到用户界面… 3 /。 最初创build的块被添加到专用于该特定重用单元的队列中。

重要的是,如果成员variables不是nil,就调用“invalidateAndCancel”函数,因为每次你想重用单元格时,前一个会话不会返回最后一个图像,而是由传入参数'url”。

之后,不要忘记在你的TableView / CollectionView单元类中放置1 /。以下片段,以便每个重用单元只使用一个imageLoader,而在方法'tableView:cellForRowAtIndexPath:'中调用该方法。你的CollectionView / TableView:

 @interface TableCell () { //...or CollectionCell :o) ImageLoader *_imgLoader; } @end @implementation TableCell - (instancetype)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; _imgLoader = [[ImageLoader alloc]init]; return self; } - (void)imageURL:(NSURL*)url { [_imgLoader getViewForCell:self.imageViewWhereTheDownloadedImageIs FromURL:url]; } @end 

完成后,只需在您的CollectionView / TableView的方法'tableView:cellForRowAtIndexPath:'中调用[cell imageURL:url]

现在,你可以尽可能快地滚动,你将永远只有这一个正确的形象。

我希望这会有所帮助。 :O)