All in the Timing: Keeping Track of Time Passed on iOS

Imagine you’re writing a game called Small Skyscraper. It’s one of a certain type of freemium game: it’s not particularly difficult, but achievements take a lot of time. You make money by selling in-app purchases to reduce the amount of time the user has to wait. Setting aside the value of this kind of game for the moment, let’s think about the game developer’s Sisyphean goal: deterring cheaters.

Cheating

(We all know, or should, that it’s impossible to completely prevent cheating. The user has all of your resources in hand. On a jailbroken device he can modify your code directly; he can modify your plists and resource files; he can spoof your server calls. All the developer can do it make cheating enough work that it’s not worth the average cheater’s effort.)

The first goal for the cheater will be the first impediment to success: the delay required to make progress in-game. He might think about trying to change the time values specified in your xml game object descriptions, or in your code; but well before that he’ll try the simplest possible cheat: changing the system clock.

It is frankly surprising how many time-based games are susceptible to this kind of cheating. The real game on which our Diminutive Domicile example is loosely based is one such game. It’s such an obvious cheating vector — why are all these games falling down in the same way? Well, it turns out this is a difficult problem on iOS. I don’t have a full answer, but I do have some information that might be useful.

Methodologies

There are a number of ways to get a number of representations of “time” on iOS. They boil down to two types: absolute and relative time. Absolute time is what you get back from [NSDate date], CFAbsoluteTimeGetCurrent(), or gettimeofday(). At the lowest level it’s expressed as seconds since the start of time: midnight on January 1, 1970 (or 2001 for the CF function). Thus each point in time is uniquely expressible as an NSDate. Relative time, on the other hand, does not have a fixed reference date. This is the time you get back from CACurrentMediaTime() or  -[NSProcessInfo systemUptime]. Because the reference time can change, CACurrentMediaTime() may return the same value at different times.

There is a clear advantage to absolute times — to wit, they are absolute. When your app suspends and resumes some time later, there’s no question about how long you’ve been out. But that’s not really true, or cheating with the system clock wouldn’t work. And indeed, this makes sense: while the user cannot change the reference date, he can change the system’s concept of when right now is. It amounts to the same thing. Thus, the disadvantage: NSDates and so on should and do respect the user’s idea of what time it is — for time zone support if nothing else — rather than the developer’s idea.

Relative times, on the other hand, do not change in response to the system clock. If CACurrentMediaTime() dropped an hour when the device moved between time zones, users watching movies would not be thrilled. This is a useful property.

It turns out that all the relative times rely on the low-level mach_absolute_time(), which is relative despite the name. It returns the time since the system booted, expressed in some machine-dependent time base that we don’t need to worry about at the moment. This is great for us — that’s certainly not something that should change in response to time zones. But it’s also not so great for us! The time since the system booted will reset if the system reboots. That means we can’t completely rely on relative times.

Solutions?

As I said, I don’t have a good answer for this question. Both relative and absolute times on the system have inherent flaws. One idea I haven’t mentioned is to get off the system: have Brief Building call back to a server process to get the absolute, un-modified time. This only works in situations where there is internet access, of course. But it suggests a hybrid solution.

I suggest implementing the server callback, and saving the “known good” server time alongside the system’s relative and absolute times. When the server is available, use that time. When it’s not, use the difference in relative times since the last known server time to extrapolate the present server time.

This covers Minuscule Mall in every situation except the user rebooting on a plane. As far as I can tell, there is no allowed way to get a more concrete relative time on an iOS device. In this situation, then, the app has a number of reasonable choices. The most draconian is to demand server access, and prevent the user from playing until the plane lands. This might not be so great if the plane is going to an international location (or even if it’s just a long trip). The most permissive option is to assume, in the absence of more reliable information, that the system’s absolute time is correct. Since this is the approach always taken by the Small Skyscraper-type games of today, it’s probably considered acceptable. A reasonable in-between might be to simply ignore the time elapsed between the last recorded time before the reboot, and the first use after. If the game hadn’t been run for a while before the reboot, though, this is still potentially a big loss to the user.

Easier on the Desktop

Things would be different if we could install daemons, or keep a process running after the game has been started, or guarantee internet access. But we can’t.

Joel Kraut

About Joel Kraut

Developing on Apple platforms for, holy shit, over ten years now. Find me on linkedin and twitter. My personal website is foon.us.
This entry was posted in Explanation and tagged , , , , , . Bookmark the permalink.