I am implementing a health bar that animates via user input.
These animations make it go up or down by a certain amount (say 50 units) and are the result of a button press. There are two buttons. Increase and Decrease.
I want to execute a lock on the health bar so that only one thread can change it at a time. The problem is I'm getting a deadlock.
I'm guessing because a separate thread runs a lock that is held by another thread. But that lock will give way when the animation completes. How do you implement a lock that ends when [UIView AnimateWithDuration] completes?
I wonder if NSConditionLock
is the way to go, but I want to use NSLocks if possible to avoid unnecessary complexity. What do you recommend?
(Eventually I want to have the animations "queue up" while letting user input continue, but for now I just want to get the lock working, even if it blocks input first.)
(Hmm come to think of it there is only one [UIView AnimateWithDuration] running at a time for the same UIView. A second call will interrupt the first, causing the completion handler to run immediately for the first. Maybe the second lock runs before the first has a chance to unlock. What's the best way to handle locking in this case? Perhaps I should revisit Grand Central Dispatch but I wanted to see if there was a simpler way.)
In ViewController.h I declare:
NSLock *_lock;
In ViewController.m I have:
In loadView:
_lock = [[NSLock alloc] init];
The rest of ViewController.m (relevant parts):
-(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;
}
}
Output:
To-Reproduce Instructions: Tap "Decrease" once
touchThreadButton1..
tryTheLock beforeLock.. free.
tryTheLock afterLock.. LOCKED. BEGIN animationBlock - val: 250 END animationBlock - val: 250 - finished: YES
tryTheLock beforeUnlock.. LOCKED.
tryTheLock afterUnlock.. free.
Conclusion: This works as expected.
--
Output:
To-Reproduce Instructions: Tap "Decrease" twice quickly (interrupting the initial animation)..
touchThreadButton1..
tryTheLock beforeLock.. free.
tryTheLock afterLock.. LOCKED. BEGIN animationBlock - val: 250 touchThreadButton1..
tryTheLock beforeLock.. LOCKED. * -[NSLock lock]: deadlock ( '(null)') * Break on _NSLockError() to debug.
Conclusion. Deadlock error. User input is frozen.