简体   繁体   English

iOS:如何阻止后台线程更新主线程中的UI?

[英]iOS: How to stop a background thread from updating UI in the main thread?

I implement a UITableView of UIImageView cells, each of which periodically refreshes itself every 5 seconds via NSTimer. 我实现了一个UITmageView UITableView单元,每个单元通过NSTimer每隔5秒定期刷新一次。 Each image is loaded from a server in the background thread, and from that background thread I also update the UI, displaying the new image, by calling performSelectorOnMainThread . 每个图像都是从后台线程中的服务器加载的,从后台线程我也通过调用performSelectorOnMainThread更新UI,显示新图像。 So far so good. 到现在为止还挺好。

Currently I face a problem when I don't wait until an image is loaded at a current cell and I scroll down quickly to a new cell. 目前,当我不等到当前单元格中加载图像并快速向下滚动到新单元格时,我遇到了问题。 That new cell, however, displays an image of a previous cell instead of the new cell's. 然而,该新单元显示先前单元的图像而不是新单元的图像。 Although the next round of NSTimer will correctly display the image, but this can confuse users at first. 虽然下一轮NSTimer会正确显示图像,但这可能会让用户感到困惑。

The problem would be disappear if I don't reuse UITableView's cells, but given the number of cells to be displayed in my app, this is not an option. 如果我不重用UITableView的单元格,问题就会消失,但考虑到我的应用程序中显示的单元格数量,这不是一个选项。

So the only solution I can think of is to cancel (or kill) the background thread that is going to display an old image if I know that a user performs the scrolling action. 因此,我能想到的唯一解决方案是,如果我知道用户执行滚动操作,则取消(或终止)将显示旧图像的后台线程。

I wonder that this might not be the best practice and, therefore, seek for your advices. 我想知道这可能不是最好的做法,因此,寻求你的意见。

(I also can't use SDWebImage because my requirement is to display a set of images in loop loaded from a server) (我也不能使用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) {
    }];
}

Ok... Another way to do that would be to put your 'image requesting code' INTO your AnimatedImageView, and invalidate pending request each time you set a new Image URL 好的...另一种方法是将“图像请求代码”放入您的AnimatedImageView中,并在每次设置新的图像URL时使待处理请求无效

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

Then, in your Cell class,'setDistantImage' creates an ASIHttpRequest, and invalidates the previous one 然后,在你的Cell类中,'setDistantImage'创建一个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...

I had the exact same problem when loading many images in CollectionView & TableView with reuse cells overlaying all the downloaded images with a quick and long scrolling. 在CollectionView和TableView中加载许多图像时,我遇到了完全相同的问题,重复单元格通过快速和长时间滚动覆盖了所有下载的图像。

For the TableView, I solved it looking at the WWDC 2012 where the 'didEndDisplayingCell' method was presented but this wasn't usefull for the CollectionView that hadn't this method. 对于TableView,我解决了它看WWDC 2012,其中提出了'didEndDisplayingCell'方法,但这对于没有这种方法的CollectionView没有用。 :o( :O(

Finally, I found out a solution for both... fortunately. 最后,我找到了两个解决方案......幸运的是。 The idea is to create a class ImageLoader called by the cell from TableView or CollectionView and that will be in charge of downloading images . 我们的想法是创建一个由TableView或CollectionView中的单元调用的类ImageLoader ,它将负责下载图像 In your implementation, add this snippet to hide the variable to the rest of the world : 在您的实现中,添加此代码段以将变量隐藏到世界其他地方:

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

Then, add the following function in your implementation : 然后,在您的实现中添加以下函数:

- (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];
}

The idea is that 1/. 这个想法是1 /。 you create a block in which the member variable _session will get the url given in incoming parameter via a downloadTask, 2/. 你创建一个块,其中成员变量_session将通过downloadTask,2 /获取传入参数中给出的url。 you display the image to the user interface via the mainQueue when it's downloaded... 3/. 您在下载时通过mainQueue将图像显示到用户界面... 3 /。 the initial created block being added to a queue dedicated to this specific reused cell. 将初始创建的块添加到专用于此特定重用单元的队列中。

The important thing is the 'invalidateAndCancel' function called if the member variable isn't nil because each time you want to reuse the cell, the former session won't bring you back the last image but the new one defined by the incoming parameter 'url'. 重要的是,如果成员变量不是nil,则调用'invalidateAndCancel'函数,因为每次要重用单元格时,前一个会话不会返回最后一个图像,而是由传入参数定义的新图像网址”。

Afterwards, don't forget to put 1/.the following snippet in your TableView/CollectionView cell class in order to use only one imageLoader for each reuse cell and 2/.the method to be called in the method 'tableView:cellForRowAtIndexPath:' of your CollectionView/TableView: 之后,不要忘记将1 /。下面的片段放在TableView / CollectionView单元类中,以便每个重用单元只使用一个imageLoader,并且在方法'tableView:cellForRowAtIndexPath:'中调用2 /。方法您的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

Once done, just call [cell imageURL:url] in the method 'tableView:cellForRowAtIndexPath:' of your CollectionView/TableView. 完成后,只需在CollectionView / TableView的方法'tableView:cellForRowAtIndexPath:'中调用[cell imageURL:url]

Now, you can scroll as quickly as you wish and you'll have always the proper image and only this one. 现在,您可以根据需要快速滚动,您将始终拥有正确的图像,只有这一个。

I hope that it will help... be well. 我希望它会有所帮助...... :o) :O)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM