停止在后台线程中执行动画并运行循环

时间:2011-12-19 09:10:54

标签: iphone ios multithreading uitableview nsrunloop

我在UITAbleViewCell中运行我的动画。 每个单元格都有自己的动画,单元格可以重复使用。 我使用[mView performSelectorInBackground:@selector(layoutSubview) withObject:nil];

在后台线程中,我启动runLoop来执行这样的任务:

- (void)startAnimation 
{
    NSRunLoop *mLoop = [NSRunLoop currentRunLoop];
    self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(setNeedsDisplay) userInfo:nil repeats:YES];

    mRunLoop = YES;
    while (mRunLoop == YES && [mLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.01]]);
}

并停止它:

- (void)stopAnimation 
{
    if (![NSThread isMainThread]) {
        [[NSThread currentThread] cancel];
    }

    mRunLoop = NO;
    self.animationTimer = nil;
    CFRunLoopStop(CFRunLoopGetCurrent());   
}

当我快速滚动表格时遇到问题,因为在第一次单元格启动时我开始动画,所以第一次运行runLoop会执行setNeedDisplay及其中的所有方法。但在完成第一个runLoop循环之前,单元格从视图中消失,并且已经可以重复使用。所以我开始清除它,而循环仍在执行操作,在这里我遇到像

这样的情况
  

消息发送到解除分配的实例

那么请你给我一些提示,告诉我应该如何正确地停止在该线程中执行操作?我的意思是,如果我想要举例说明一个对象,它正在执行一些如何立即停止的动作?

希望我提供足够的信息。 感谢

更新:根本没有想法?

4 个答案:

答案 0 :(得分:2)

我会采取完全不同的方式:

完全摆脱单元格的计时器和后台线程!

动画不是NSTimer最适合的地方,而且多个计时器也无济于事。

UITableView有一个方法visibleCells和一个方法indexPathsForVisibleRows。我建议使用单个CADisplayLink - 其中 适用于动画,因为它会以显示器的实际刷新率或其一小部分调用回来 - 在tableview-controller中并且在该显示链接的回调中迭代可见单元格。

如果你想在辅助线程的运行循环上安排display-link,请随意这样做,但我会检查你是否可以先没有额外的线程就离开。

一些代码:

@interface AnimatedTableViewController ()

@property (strong, nonatomic) CADisplayLink *cellAnimator;
- (void)__cellAnimatorFired:(CADisplayLink *)animator;

@end

@implementation AnimatedTableViewController

@synthesize cellAnimator = cellAnimator_;

- (void)setCellAnimator:(CADisplayLink *)animator
{
    if (animator == cellAnimator_)
        return;

    [cellAnimator_ invalidate];
    cellAnimator_ = animator;

    [cellAnimator_ addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSCommonRunLoopModes];
}

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    self.cellAnimator = [CADisplayLink displayLinkWithTarget:self selector:@selector(__cellAnimatorFired:)];
    ...
}

- (void)viewWillDisappear:(BOOL)animated
{
    self.cellAnimator = nil;
    ...

    [super viewWillDisappear:animated];
}

- (void)__cellAnimatorFired:(CADisplayLink *)animator
{
     NSArray *visibleCells = [self.tableView visibleCells];
     [visibleCells enumerateObjectsUsingBlock:^(UITableViewCell *cell, NSUInteger unused, BOOL *stop){
         [cell setNeedsDisplay];
     }];
}

...

@end

答案 1 :(得分:2)

NSTimer有一个-cancel方法可以停止计时器的触发。在-prepareForReuse中调用它(并且就此而言,在-stopAnimation中)可能有所帮助。

但是,这段代码看起来很危险。像这样嵌套运行循环几乎从来都不是一个好主意 - 而且,据我所知,这是完全没必要的。如果让-startAnimation返回,则动画计时器仍会在主运行循环上运行。如果你这样做是因为在-startAnimation之后有一些你希望延迟的代码,你应该重构代码,这样就不需要了。

(如果删除-startAnimation中的runloop内容,请不要在-stopAnimation中停止runloop。)

像danyowdee推荐的方法会更好,但至少要摆脱这种runloop的东西。这只是在寻找麻烦。

答案 2 :(得分:0)

我认为您可以将此方法用于您的问题

[NSObject cancelPreviousPerformRequestsWithTarget:yourTarget selector:aSelector object: anArgument];

答案 3 :(得分:0)

我认为避免该行为的最佳方法是在其他不会重用的类中分配接收cancel方法的委托。例如,您可以拥有一个处理所有cancel方法的私有实例数组,每个行都映射到一个数组元素。

我建议您使用Apple在Xcode文档中提供的惰性表示例。这是一个很好的例子,说明如何使用表格在背景中异步加载图像。我认为对于滚动主题(减速和分页)也很有用。

只有一个考虑,我不建议搞乱几个cfrunloopstop,测试一下!