Use of ios GCD timer (start, pause, resume, close)

1. Advantages of GCD timer

The GCD timer actually uses the dispatch source, which listens to the system kernel objects and processes them. Dispatch is similar to the producer consumer mode. By listening to the system kernel object, the corresponding dispatch queue is automatically notified to execute after the producer's production data, and the latter acts as the consumer. More accurate through system level call.

GCD Timer is dispatch_source_t Type of variable, which can achieve more accurate timing effect. Let's see how to use it:
/** Create timer object
 * para1: DISPATCH_SOURCE_TYPE_TIMER Is timer type
 * para2-3: The middle two parameters are useless for the timer
 * para4: Finally, for what scheduling queue to use
 */
_gcdTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(0, 0));
/** set timer 
 * para2: Task start time
 * para3: Interval between tasks
 * para4: Acceptable error time, setting 0 means no error is allowed
 * Tips: In nanoseconds
 */
dispatch_source_set_timer(_gcdTimer, DISPATCH_TIME_NOW, 2.0 * NSEC_PER_SEC, 0.0 * NSEC_PER_SEC);
/** Set timer task
 * You can use block mode
 * You can also use C function
 */
dispatch_source_set_event_handler(_gcdTimer, ^{
    static int gcdIdx = 0;
    NSLog(@"GCD Method: %d", gcdIdx++);
    NSLog(@"%@", [NSThread currentThread]);
    
    if(gcdIdx == 5) {
        // Termination timer
        dispatch_suspend(_gcdTimer);
    }
});
// Start task, GCD timer needs to be started manually after creation
dispatch_resume(_gcdTimer);

GCD is more punctual:

  • Using GCD for countdown
    There is an advantage of writing with GCD. Page skipping will not clear page skipping will cause countdown error
  • Avoid too much time-consuming operation concurrency
  • Using GCD timer
  • Create a new thread and start RunLoop, add timer to it (use appropriately)
  • Add timer to nsrnloopcommonmodes (improper use will block UI response)

2. Use (upper code)

/**
 *  Get the string of the day
 *  @return Format is year month day hour minute second
 */
- (NSString *)getCurrentTimeyyyymmdd {
    
    NSDate *now = [NSDate date];
    NSDateFormatter *formatDay = [[NSDateFormatter alloc] init];
    formatDay.dateFormat = @"mm:ss";
    NSString *dayStr = [formatDay stringFromDate:now];
    
    return dayStr;
}

/**
 *  Get time difference deadline - current time
 *  nowDateStr : current time
 *  deadlineSecond : Timer duration (unit / s)
 *  @return Time stamp difference
 */
- (NSInteger)getDateDifferenceWithNowDateStr:(NSString*)nowDateStr deadlineSecond:(CGFloat)deadlineSecond {
    
    NSInteger timeDifference = 0;
    
    NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
    [formatter setDateFormat:@"mm:ss"];
    NSDate *nowDate = [formatter dateFromString:nowDateStr];
    NSTimeInterval oldTime = [nowDate timeIntervalSince1970];
    NSTimeInterval newTime = [nowDate timeIntervalSince1970] + deadlineSecond;
    timeDifference = newTime - oldTime;
    
    return timeDifference;
}
- (void)startTimer{
    // Countdown time test data
    CGFloat deadlineSecond = 10; //Unit second
    // Timestamp of current time
    NSString *nowStr = [self getCurrentTimeyyyymmdd];
    NSLog(@"nowStr:%@",nowStr);
    // Calculate time difference
    CGFloat secondsCountDown = [self getDateDifferenceWithNowDateStr:nowStr deadlineSecond:deadlineSecond];
    NSLog(@"secondsCountDown:%f",secondsCountDown);
    [self startProgressAction:secondsCountDown];
    //Dispatch_source_t timer; / / timer definition, this sentence is written in @ interface xxx ~~~
    
    __weak __typeof(self) weakSelf = self;
    
    if (_timer == nil) {
        __block NSInteger timeout = secondsCountDown; // Countdown time
        
        if (timeout!=0) {
            dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
            _timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
            dispatch_source_set_timer(_timer, dispatch_walltime(NULL, 0), 1.0*NSEC_PER_SEC,  0); //Execution per second
            dispatch_source_set_event_handler(_timer, ^{
                if(timeout <= 0){ //  At the end of the countdown, do the following:
                    dispatch_source_cancel(weakSelf.timer);
                    weakSelf.timer = nil;
                    dispatch_async(dispatch_get_main_queue(), ^{
                        weakSelf.restTime.text = @"woooooooo";
                        weakSelf.errorLabel.hidden = NO;				//Here is what you need to do when the time is up
                    });
                } else { // Countdown recalculate hour / minute / second
                    NSInteger days = (int)(timeout/(3600*24));
                    NSInteger hours = (int)((timeout-days*24*3600)/3600);
                    NSInteger minute = (int)(timeout-days*24*3600-hours*3600)/60;
                    NSInteger second = timeout - days*24*3600 - hours*3600 - minute*60;
                    NSString *strTime = [NSString stringWithFormat:@"Time remaining:%02ld branch: %02ld second", minute, second];
                    dispatch_async(dispatch_get_main_queue(), ^{
                        weakSelf.restTime.text = strTime;				//This is the operation you need to perform during the time recording
                    });
                    timeout--; // Decrement countdown-1 (total time in seconds)
                }
            });
            dispatch_resume(_timer);
        }
        
    }
}

-(void) pauseTimer{
    if(self.timer){
        dispatch_suspend(_timer);
    }
}
-(void) resumeTimer{
    if(self.timer){
        dispatch_resume(_timer);
    }
}
-(void) stopTimer{
    if(self.timer){
        dispatch_source_cancel(_timer);
        _timer = nil;
    }
}

If you only follow the above method, when the App enters the background, the timer will automatically pause. In order to keep the timer running, we need to add the following code:

The principle is as follows: https://blog.csdn.net/rhddlr/article/details/89488559

- (void)applicationDidEnterBackground:(UIApplication *)application {
    // Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
    // If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
        
        UIApplication*   app = [UIApplication sharedApplication];
        __block    UIBackgroundTaskIdentifier bgTask;
        bgTask = [app beginBackgroundTaskWithExpirationHandler:^{
            dispatch_async(dispatch_get_main_queue(), ^{
                if (bgTask != UIBackgroundTaskInvalid)
                {
                    bgTask = UIBackgroundTaskInvalid;
                }
            });
        }];
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            dispatch_async(dispatch_get_main_queue(), ^{
                if (bgTask != UIBackgroundTaskInvalid)
                {
                    bgTask = UIBackgroundTaskInvalid;
                }
            });
        });
    
}

Posted by mohamed42 on Sat, 23 Nov 2019 09:51:55 -0800