2

我在 UITTableViewCell 中运行我的动画。每个单元格都有自己的动画,并且单元格是可重复使用的。我用[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

4 回答 4

2

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

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

动画并不是NSTimer一开始就很适合的东西,拥有多个计时器也无济于事。

UITableView有一个方法visibleCells和一个方法indexPathsForVisibleRows。我建议在您的 tableview-controller 和该 display-link 的回调中使用单个CADisplayLink- 它适用于动画,因为它会以显示器的实际刷新率或其一小部分回调您可见细胞。

如果您想在辅助线程的运行循环上安排显示链接,请随意这样做,但我会先检查您是否可以在没有额外线程的情况下逃脱。

一些代码:

@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
于 2011-12-26T15:04:48.757 回答
2

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

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

(如果您将 runloop 的内容放入 中-startAnimation,也不要在其中停止 runloop -stopAnimation。)

像 danyowdee 推荐的方法会更好,但至少摆脱这种运行循环的东西。这只是自找麻烦。

于 2011-12-28T11:08:25.623 回答
0

我认为您可以使用此方法解决您的问题

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

于 2011-12-20T05:19:40.087 回答
0

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

我向您推荐 Apple 在 Xcode 文档中提供的惰性表示例。这是一个很好的例子,展示了如何在后台使用表格异步加载图像。我认为这对滚动主题(减速和分页)也很有用。

再多考虑一个,不建议搞乱几个cfrunloopstop,努力测试吧!

于 2011-12-28T10:40:34.807 回答