1

I have a modal view in which a countdown starts when the user had performed some action. At the end of the countdown, the modal view would close. The following is the procedure I have written towards that goal but unfortunately, it is causing some thread problems. Is there any way to rewrite this in a way that does not have potential thread issues?

- (void)countDown {
    static int i = 3;
    if (i == 3) {
        i--;
        UIImage *three = [UIImage imageNamed:@"Three"];
        countDownFlag = [[UIImageView alloc] initWithImage:three];
        countDownFlag.frame = CGRectMake(0, 370, countDownFlag.frame.size.width, countDownFlag.frame.size.height);
        countDownFlag.center = CGPointMake(width / 2, countDownFlag.center.y);
        [self.view addSubview:countDownFlag];
        [self performSelector:@selector(countDown) withObject:nil afterDelay:0.5];
    } else if (i == 2) {
        i--;
        UIImage *two = [UIImage imageNamed:@"Two"];
        [countDownFlag setImage:two];
        [self performSelector:@selector(countDown) withObject:nil afterDelay:0.5];
    } else if (i == 1) {
        i--;
        UIImage *one = [UIImage imageNamed:@"One"];
        [countDownFlag setImage:one];
        [self performSelector:@selector(countDown) withObject:nil afterDelay:0.5];
    } else {
        [self dismissViewControllerAnimated:YES completion:nil];
    }
}

Edit: the picture shows what XCode tells me about the problem

output

More edit:

My code has changed to reflect vadian's answer. Here is the update

- (void)startCountDown {
    NSLog(@"starting count down");
    counter = 3;
    [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(countDown:) userInfo:nil repeats:YES];
}

- (void)countDown:(NSTimer *)timer {

    if (counter == 3) {
        countDownFlag = [[UIImageView alloc] init];
        countDownFlag.frame = CGRectMake(0, 370, countDownFlag.frame.size.width, countDownFlag.frame.size.height);
        countDownFlag.center = CGPointMake(width / 2, countDownFlag.center.y);
        dispatch_async(dispatch_get_main_queue(), ^{
            [self.view addSubview:countDownFlag];
        });
    } else if (counter == 0) {
        [timer invalidate];
        [self dismissViewControllerAnimated:YES completion:nil];
        return;
    }
    NSArray *imageNameArray = @[@"", @"One", @"Two", @"Three"];
    UIImage *image = [UIImage imageNamed:imageNameArray[counter]];
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.countDownFlag setImage:image];
    });
    counter--;
}

I new guess that the problem probably lies in the code that calls the countdown. Here is the code that does this.

- (void)changeLanguage:(BOOL) isMyanmar {
    if (hasRun == YES) {
        return;
    }
    hasRun = YES;

    NSUserDefaults * userdefaults = [NSUserDefaults standardUserDefaults];
    [userdefaults setBool:isMyanmar forKey:@"myanmar"];
    [userdefaults synchronize];

    [self updateCurrentLanguageText];
    if ([self isMyanmar]) {
        [self changeLanguageFlag:@"MyanmarFlagBig"];
    } else {
        [self changeLanguageFlag:@"UnitedKingdomFlagBig"];
    }

    //>>>>>>>>>>>>>>>>>>>> the problem is probably here

    [self startCountDown];
}

Any code that runs after changing language code fails. The problem is probably there.

Edit: the thread problem is gone but the countdown isn't happening anymore.

After I have moved the code in changeLanguage into the methods that call it, the problem is mysteriously gone. (Repetitive but it works.)

- (void)changeLanguageToEnglish {
    [theLock lock];
    if (hasRun == YES) {
        [theLock unlock];
        return;
    }
    hasRun = YES;

    [userdefaults setBool:false forKey:@"myanmar"];
    [userdefaults synchronize];

    [self updateCurrentLanguageText];
    if ([self isMyanmar]) {
        [self changeLanguageFlag:@"MyanmarFlagBig"];
    } else {
        [self changeLanguageFlag:@"UnitedKingdomFlagBig"];
    }

    [self startCountDown];

    [theLock unlock];

}

- (void)changeLanguageToMyanmar {
    [theLock lock];
    if (hasRun == YES) {
        [theLock unlock];
        return;
    }
    hasRun = YES;

    [userdefaults setBool:true forKey:@"myanmar"];
    [userdefaults synchronize];

    [self updateCurrentLanguageText];
    if ([self isMyanmar]) {
        [self changeLanguageFlag:@"MyanmarFlagBig"];
    } else {
        [self changeLanguageFlag:@"UnitedKingdomFlagBig"];
    }

    [self startCountDown];

    [theLock unlock];

}

But the problem is that the countdown isn't happening anymore.

Sandah Aung
  • 6,156
  • 15
  • 56
  • 98

2 Answers2

2

You could use an NSTimer to perform the countdown and a static variable for the counter.

The method startCountDown starts the timer.

The method countDown:(NSTimer *)timer is called every 0.5 seconds. The passed NSTimer argument is exactly the timer which has been started. it performs the action according to its value and decrements the counter. If the counter is 0, the timer is invalidated and the controller dismissed.

static UInt8 counter = 0;

- (void)startCountDown {
  countDownFlag = [[UIImageView alloc] init];
  countDownFlag.frame = CGRectMake(0, 370, countDownFlag.frame.size.width, countDownFlag.frame.size.height);
  countDownFlag.center = CGPointMake(width / 2, countDownFlag.center.y);
  [self.view addSubview:countDownFlag];
  counter = 3;
  [NSTimer scheduledTimerWithTimeInterval:0.5 target:self selector:@selector(countDown:) userInfo:nil repeats:YES];
}

- (void)countDown:(NSTimer *)timer {
  if (counter == 0) {
    [timer invalidate];
    [self dismissViewControllerAnimated:YES completion:nil];
    return;
  }
  NSArray *imageNameArray = @[@"", @"One", @"Two", @"Three"];
  UIImage *image = [UIImage imageNamed:imageNameArray[counter]];
  dispatch_async(dispatch_get_main_queue(), ^{
    [self.countDownFlag setImage:image];
  });
  counter--;

}

vadian
  • 274,689
  • 30
  • 353
  • 361
  • `timer.userInfo = @{@"counter" : @(counter)};` is causing errors – Sandah Aung Jul 10 '15 at 11:52
  • Thanks for simplifying my code but the thread problem is still there. – Sandah Aung Jul 10 '15 at 12:01
  • I edited the code again adding `dispatch_async()` blocks to perform the UI related code on the main thread – vadian Jul 10 '15 at 12:05
  • your code is probably right. The probably might probably lie somewhere else. I have updated my question to point out the problem. – Sandah Aung Jul 10 '15 at 12:28
  • have you set an Exception Breakpoint to catch the error at a more specific place? – vadian Jul 10 '15 at 12:45
  • I have. The problem is caused by setting the image. – Sandah Aung Jul 10 '15 at 13:17
  • 1
    I edited the code again to put the creation of the image view out of the `countDown:` method. Try to insert a couple of `NSLog` lines to narrow the line where the error occurs – vadian Jul 10 '15 at 13:23
  • Thanks for trying to help. I truly appreciate your effort. After changing the code back to original (with language change logic moved to the caller methods), the program is working again. – Sandah Aung Jul 11 '15 at 14:56
1

Place static int i = 3; outside your method countDown, the way you did it it declares new variable every time you call that method.

Uros19
  • 391
  • 2
  • 11
  • Relocating `static int i = 3;` doesn't solve the problem. [The values of static local variables are remembered across method invocations.](http://stackoverflow.com/questions/1087061/whats-the-meaning-of-static-variables-in-an-implementation-of-an-interface) – Sandah Aung Jul 10 '15 at 11:44