Локальный встроенный таймер обратного отсчета времени поиска iOS

Я бы хотел создать локальный таймер обратного отсчета времени с автономным временем работы iOS, например, для подсчета времени простоя для следующей награды в играх.

Объяснение объекта:

локальный
- Работает без подключения к Интернету

нативной IOS
- Я хочу найти решение в (желательно) Objective-C или Swift

время-взломать
- Когда пользователь меняет время своего устройства вперед/назад, оставшееся время остается тем же самым

фон
- Отключение/повторное открытие дружественных

таймер обратного отсчета
- Таймер, который после запуска предоставит метод проверки оставшегося времени

Я хотел бы иметь систему вознаграждений, основанных на времени, в моей игре на iOS, которая не будет легко взламываться, просто перемещая время устройства.

Первоначально я думал, что это невозможно, и продолжаю использовать [NSDate date] и savedDate, сравнивая их вместе, чтобы найти прошедшее время. В последнее время, однако, я столкнулся с игрой, которая использует эту функцию: Crossy Road. Насколько я знаю, это игра Unity, но я сомневаюсь, что некоторая общая функция, подобная этой, доступная в проекте Unity, которая скомпилирована в приложение iOS, недоступна в iOS через Objective-C или Swift.

Они предоставляют систему вознаграждения по времени, один раз в каждые 6 часов, и, насколько я проверял, он не взламывается, изменяя время устройства. Он также работает без подключения к Интернету. (Я не уверен, работает ли это, если вы никогда не подключаетесь, но когда я подключился, отключился и попытался использовать хак, это не сработало.)

Ответ 1

Вы можете использовать текущее время безотказной работы системы (mach_absolute_time()). Эта функция легко доступна с помощью CACurrentMediaTime(), которая возвращает ее удобно в секундах. Тогда вам, вероятно, придется настроить какой-то интервал, чтобы проверить это значение. Конечно, это можно сделать с помощью NSDate, так как это не проблема, вызванная тем, что пользователь настраивает часы вперед. Когда дело доходит до перезагрузки, просто сохраните начальное значение и используйте это как смещение после перезагрузки.

Единственный недостаток заключается в том, что он не учитывает время отключения устройства, но в наши дни это обычно не очень важно, когда никто не поворачивает свои телефоны.

Чтобы получить фактическую функцию таймера, просто сохраните значение в начале и затем регулярно проверяйте его на прогнозируемое конечное значение, возможно, с увеличением разрешения по мере приближения конца.

Ответ 2

У меня были такие же требования для приложения, которое я создаю. Я пробовал использовать CACurrentMediaTime(), но когда вы выключаете устройство, оно сбрасывается до 0, что не то, что вы хотите. То, что работало даже при выключении устройства, заключалось в использовании этой ссылки времени:

currentUser.timeLastUpdated = [[NSDate date] timeIntervalSince1970];

Здесь приведен код, который поможет вам реализовать таймер. Это изменено из другого потока stackoverflow.

В MainViewController.m

@interface MainViewController ()

@property (strong, nonatomic) IBOutlet UILabel *countdownLabel;

@end

@implementation MainViewController
{
    UserData *_currentUser;
    int hours;
    int minutes;
    int seconds;
}

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    _currentUser = [UserData currentUser];
    [self updateTimerText];
    [self startTimer];
}

- (void)startTimer {
    [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(updateTimer:) userInfo:nil repeats:YES];
}

- (void)updateTimer:(NSTimer *)timer {
    if (_currentUser.secondsLeft > 0) {
        _currentUser.secondsLeft--;
        [self updateTimerText];
    }
}

- (void)updateTimerText {
    int secondsLeft = _currentUser.secondsLeft;
    hours = secondsLeft / 3600;
    minutes = (secondsLeft%3600) / 60;
    seconds = (secondsLeft%3600) % 60;
    self.countdownLabel.text = [NSString stringWithFormat:@"%02d:%02d:%02d",hours,minutes,seconds];
}

}

Теперь, чтобы сделать его пуленепробиваемым, чтобы он не переставал считаться, когда вы останавливаете приложение, вам нужно изменить AppDelegate.m.

В AppDelegate.m

@implementation AppDelegate
{
    UserData *currentUser;
}

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // Override point for customization after application launch.

    // Intialize currentUser
    currentUser = [UserData currentUser];

    // Reset secondsLeft based on lastTimeUpdated
    if (currentUser.secondsLeft > 0 && currentUser.lastTimeUpdated != 0) {
        currentUser.secondsLeft -= ((int)[[NSDate date] timeIntervalSince1970]) - currentUser.lastTimeUpdated;
    }

    return YES;
}

- (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.

    // Set the last time updated (only need to call it here because this is coupled with applicationWillTerminate
    currentUser.lastTimeUpdated = (int)[[NSDate date] timeIntervalSince1970];
}

- (void)applicationWillEnterForeground:(UIApplication *)application {
    // Called as part of the transition from the background to the active state; here you can undo many of the changes made on entering the background.

    // Reset secondsLeft based on lastTimeUpdated
    if (currentUser.secondsLeft > 0 && currentUser.lastTimeUpdated != 0) {
        currentUser.secondsLeft -= ((int)[[NSDate date] timeIntervalSince1970]) - currentUser.lastTimeUpdated;
    }

}

Вам нужно только изменить три метода в AppDelegate, потому что ApplicationWillTerminate всегда запускается с ApplicationDidEnterBackground (насколько я могу судить).

Взаимодействие currentUser.secondsLeft и currentUser.lastTimeUpdated - это ints. Я выбрал [[NSDate date] timeIntervalSince1970] как int, потому что он возвращает CFTimeInterval, который является двойником типа.

Наконец, вы должны обязательно сохранить его в базе данных. Я лениво сохраняю эти две переменные в NSUserDefaults, когда они обновляются, включая следующий код в файле UserData.m:

В UserData.m

-(void)setSecondsLeft:(int)secondsLeft {
    _secondsLeft = secondsLeft;
    [[NSUserDefaults standardUserDefaults] setObject:@(secondsLeft) forKey:@"secondsLeft"];
}

-(void)setLastTimeUpdated:(int)lastTimeUpdated {
    _lastTimeUpdated = lastTimeUpdated;
    [[NSUserDefaults standardUserDefaults] setObject:@(lastTimeUpdated) forKey:@"lastTimeUpdated"];

Таким образом, когда приложение завершает работу, данные сохраняются для последующего использования.