gpt4 book ai didi

ios - 如何执行等待动画完成的锁?

转载 作者:可可西里 更新时间:2023-11-01 03:41:57 24 4
gpt4 key购买 nike

我正在实现一个通过用户输入动画的健康栏。

这些动画使它上升或下降一定数量(比如 50 个单位),并且是按下按钮的结果。有两个按钮。增加和减少。

我想对健康栏执行锁定,以便一次只有一个线程可以更改它。问题是我陷入了僵局。

我猜是因为一个单独的线程运行另一个线程持有的锁。但是当动画完成时那个锁就会让位。你如何实现在 [UIView AnimateWithDuration] 完成时结束的锁?

不知道是不是NSConditionLock是要走的路,但我想尽可能使用 NSLocks 以避免不必要的复杂性。你有什么建议吗?

(最终我想让动画“排队”,同时让用户输入继续,但现在我只想让锁工作,即使它首先阻止输入。)

(嗯,想想看,同一个 UIView 一次只运行一个 [UIView AnimateWithDuration]。第二次调用会中断第一个调用,导致第一个调用完成处理程序立即运行。也许第二个锁在首先有机会解锁。在这种情况下,处理锁定的最佳方法是什么?也许我应该重新访问 Grand Central Dispatch,但我想看看是否有更简单的方法。)

在 ViewController.h 我声明:

NSLock *_lock;

在 ViewController.m 我有:

在加载 View 中:
_lock = [[NSLock alloc] init];

ViewController.m 的其余部分(相关部分):
-(void)tryTheLockWithStr:(NSString *)str
{
LLog(@"\n");
LLog(@" tryTheLock %@..", str);

if ([_lock tryLock] == NO)
{
NSLog(@"LOCKED.");
}
else
{
NSLog(@"free.");
[_lock unlock];
}
}

// TOUCH DECREASE BUTTON
-(void)touchThreadButton1
{
LLog(@" touchThreadButton1..");

[self tryTheLockWithStr:@"beforeLock"];
[_lock lock];
[self tryTheLockWithStr:@"afterLock"];

int changeAmtInt = ((-1) * FILLBAR_CHANGE_AMT);
[self updateFillBar1Value:changeAmtInt];

[UIView animateWithDuration:1.0
delay:0.0
options:(UIViewAnimationOptionTransitionNone|UIViewAnimationOptionBeginFromCurrentState|UIViewAnimationOptionAllowUserInteraction)
animations:
^{

LLog(@" BEGIN animationBlock - val: %d", self.fillBar1Value)
self.fillBar1.frame = CGRectMake(FILLBAR_1_X_ORIGIN,FILLBAR_1_Y_ORIGIN, self.fillBar1Value,30);
}
completion:^(BOOL finished)
{
LLog(@" END animationBlock - val: %d - finished: %@", self.fillBar1Value, (finished ? @"YES" : @"NO"));

[self tryTheLockWithStr:@"beforeUnlock"];
[_lock unlock];
[self tryTheLockWithStr:@"afterUnlock"];
}
];
}

-(void)updateFillBar1Value:(int)changeAmt
{
self.prevFillBar1Value = self.fillBar1Value;

self.fillBar1Value += changeAmt;

if (self.fillBar1Value < FILLBAR_MIN_VALUE)
{
self.fillBar1Value = FILLBAR_MIN_VALUE;
}
else if (self.fillBar1Value > FILLBAR_MAX_VALUE)
{
self.fillBar1Value = FILLBAR_MAX_VALUE;
}
}

输出:

复制说明:点击“减少”一次

触摸线程按钮1..

tryTheLock beforeLock..
自由。

tryTheLock afterLock..
锁定。
开始动画块 - val: 250
END animationBlock - val:250 - 完成:是

在解锁之前尝试锁定..
锁定。

解锁后尝试锁定..
自由。

结论:这按预期工作。

——

输出:

重现说明:快速点击“减少”两次(中断初始动画)。

触摸线程按钮1..

tryTheLock beforeLock..
自由。

tryTheLock afterLock..
锁定。
开始动画块 - val: 250
触摸线程按钮1..

tryTheLock beforeLock..
锁定。
* -[NSLock 锁]: 死锁 ('(null)')
*
中断 _NSLockError() 进行调试。

结论。死锁错误。用户输入被卡住。

最佳答案

在底部,在我的原始答案中,我描述了一种实现所请求功能的方法(如果您在前一个动画仍在进行时启动动画,则将此后续动画排队以仅在当前动画完成后开始)。

虽然我会出于历史目的保留它,但我可能想建议一种完全不同的方法。具体来说,如果您点击一个应该产生动画的按钮,但前一个动画仍在进行中,我建议您删除旧动画并立即开始新动画,但这样做的方式是新动画动画从当前停止的地方开始。

  • 在 iOS 8 之前的 iOS 版本中,挑战在于如果您在另一个动画正在进行时开始一个新动画,操作系统会尴尬地立即跳转到当前动画本应结束的位置并从那里开始新动画。

    8 之前的 iOS 版本中的典型解决方案是:
  • presentationLayer动画 View 的当前状态(这是 CALayerUIView 的当前状态...如果您在动画进行时查看 UIView,您将看到最终值,我们需要获取当前状态);
  • presentationLayer 中获取动画属性值的当前值;
  • 删除动画;
  • 将动画属性重置为“当前”值(这样它就不会在开始下一个动画之前跳到前一个动画的结尾);
  • 将动画初始化为"new"值;

  • 因此,例如,如果您正在为 frame 的变化设置动画这可能正在进行动画,您可能会执行以下操作:
    CALayer *presentationLayer = animatedView.layer.presentationLayer;
    CGRect currentFrame = presentationLayer.frame;
    [animatedView.layer removeAllAnimations];
    animatedView.frame = currentFrame;
    [UIView animateWithDuration:1.0 animations:^{
    animatedView.frame = newFrame;
    }];

    这完全消除了与在“当前”动画(和其他排队的动画)完成后将“下一个”动画排队运行相关的所有尴尬。您最终也会获得响应速度更快的 UI(例如,您不必在用户所需的动画开始之前等待先前的动画完成)。
  • 在 iOS 8 中,这个过程非常简单,如果你启动一个新动画,它通常不仅会从动画属性的当前值开始动画,还会识别当前动画的速度属性正在改变,导致旧动画和新动画之间的无缝过渡。

    有关 iOS 8 新功能的更多信息,我建议您引用 WWDC 2014 视频 Building Interruptible and Responsive Interactions .

  • 为了完整起见,我将在下面保留我的原始答案,因为它试图精确地解决问题中概述的功能(只是使用不同的机制来确保主队列不被阻塞)。但我真的建议考虑停止当前动画并以这样一种方式开始新动画,即它从任何正在进行的动画可能停止的地方开始。

    原答案:

    我不建议将动画包装在 NSLock 中(或信号量,或任何其他类似机制),因为这会导致主线程阻塞。你永远不想阻塞主线程。我认为您关于使用串行队列调整大小操作的直觉很有希望。您可能想要一个“调整大小”操作:
  • 发起 UIView主队列上的动画(所有 UI 更新都必须在主队列上进行);和
  • 在动画的完成块中,完成操作(我们直到那时才完成操作,以确保其他排队的操作在此完成之前不会启动)。

  • 我可能会建议调整大小操作:

    大小操作.h:
    @interface SizeOperation : NSOperation

    @property (nonatomic) CGFloat sizeChange;
    @property (nonatomic, weak) UIView *view;

    - (id)initWithSizeChange:(NSInteger)change view:(UIView *)view;

    @end

    SizingOperation.m:
    #import "SizeOperation.h"

    @interface SizeOperation ()

    @property (nonatomic, readwrite, getter = isFinished) BOOL finished;
    @property (nonatomic, readwrite, getter = isExecuting) BOOL executing;

    @end

    @implementation SizeOperation

    @synthesize finished = _finished;
    @synthesize executing = _executing;

    - (id)initWithSizeChange:(NSInteger)change view:(UIView *)view
    {
    self = [super init];
    if (self) {
    _sizeChange = change;
    _view = view;
    }
    return self;
    }

    - (void)start
    {
    if ([self isCancelled] || self.view == nil) {
    self.finished = YES;
    return;
    }

    self.executing = YES;

    // note, UI updates *must* take place on the main queue, but in the completion
    // block, we'll terminate this particular operation

    [[NSOperationQueue mainQueue] addOperationWithBlock:^{
    [UIView animateWithDuration:2.0 delay:0.0 options:kNilOptions animations:^{
    CGRect frame = self.view.frame;
    frame.size.width += self.sizeChange;
    self.view.frame = frame;
    } completion:^(BOOL finished) {
    self.finished = YES;
    self.executing = NO;
    }];
    }];
    }

    #pragma mark - NSOperation methods

    - (void)setExecuting:(BOOL)executing
    {
    [self willChangeValueForKey:@"isExecuting"];
    _executing = executing;
    [self didChangeValueForKey:@"isExecuting"];
    }

    - (void)setFinished:(BOOL)finished
    {
    [self willChangeValueForKey:@"isFinished"];
    _finished = finished;
    [self didChangeValueForKey:@"isFinished"];
    }

    @end

    然后为这些操作定义一个队列:
    @property (nonatomic, strong) NSOperationQueue *sizeQueue;

    确保实例化此队列(作为串行队列):
    self.sizeQueue = [[NSOperationQueue alloc] init];
    self.sizeQueue.maxConcurrentOperationCount = 1;

    然后,任何使有问题的观点成长的事情都会做:
    [self.sizeQueue addOperation:[[SizeOperation alloc] initWithSizeChange:+50.0 view:self.barView]];

    任何使有问题的 View 缩小的事情都会做:
    [self.sizeQueue addOperation:[[SizeOperation alloc] initWithSizeChange:-50.0 view:self.barView]];

    希望这说明了这个想法。有各种可能的改进:
  • 我让动画变得很慢,所以我可以轻松地将一大堆动画排队,但你可能会使用更短的值;
  • 如果使用自动布局,您将调整宽度约束的 constant并在动画块中执行 layoutIfNeeded ) 而不是直接调整框架;和
  • 如果宽度达到某些最大值/最小值,您可能想要添加检查以不执行框架更改。

  • 但关键是使用锁来控制UI变化的动画是不可取的。除了几毫秒之外,您不希望任何可能阻塞主队列的东西。动画块太长,无法考虑阻塞主队列。因此,请使用串行操作队列(如果您有多个线程需要发起更改,它们都只会将一个操作添加到同一个共享操作队列中,从而自动协调从各种不同线程发起的更改)。

    关于ios - 如何执行等待动画完成的锁?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18422645/

    24 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com