It is inevitable to deal with time when making App. There are many ways to deal with time, which is far from a line of API calls and obtaining the current system time. We need to understand the differences between the various time-related apis and design the mechanics for each scenario.

Form of time

Before we dive into this, we need to make sure of one premise: time is linear. That is, at any moment, there is only one absolute time value on the earth, but because of the difference in time zone or culture, we in the same space and time express or understand the same time differently. This seemingly straightforward principle is the cornerstone of our understanding of all sorts of complex concepts related to time. Just as UTF-8 and UTF-16 are Unicode, 20:00 in Beijing and 21:00 in Tokyo are the same absolute time values.

GMT

Our understanding of time is limited, but we know for certain that it moves at a constant rate. Time moves at a uniform speed, not up or down, so to describe time we need to find a value that moves at a uniform speed.

It may surprise you, but we humans have been searching for this reference point to accurately describe the current value of time for years. You can try to think about things in life that change evenly over time, that have numerical properties that change at an absolute constant rate over time.

Previous studies have found that it’s a good idea to look up at the sun, which rises early and sets late at the same time. The sun is “immutable” and can be described in terms of the sun’s position during the day. Later, cultures in different regions needed to communicate. The sun was shining high in your area, while I might have gone down the mountain, so there needed to be a public place that everyone could agree on. It would be convenient to communicate with each other based on the location of the sun. The final choice is the Greenwich Observatory in London, UK. Greenwich Time is the public Time, also known as Greenwich Mean Time.

UTC

The position of the sun changes associated with the earth’s rotation, in the past, people thought that the earth’s rotation rate is constant, but the cognition was overturned in 1960, it was found that the earth’s rotation rate is becoming more and more slowly, and time that the forward rate is still a constant, so the UTC will no longer be that can be used to accurately describe the time.

We need to keep looking for a constant velocity. From macro to look up into the sky is our direction to find the answer, the development of science and technology let us made a deeper understanding in the micro aspect, and there the wise according to microscopic particles in the physical properties of the atom, established the atomic clocks, as measured by the atomic clocks time, atomic clocks 5 billion error 1 second, the intensive reading has been far more than GMT. This atomic clock reflects Time, which is Coordinated Universal Time (UTC).

Let’s take a look at the various ways in which iOS keeps track of time.

NSDate

NSDate class NSDate class NSDate class NSDate class

NSDate objects encapsulate a single point in time, independent of any particular calendrical system or time zone. Date objects are immutable, representing an invariant time interval relative to an absolute reference date (00:00:00 UTC on 1 January 2001).

The NSDate object describes an absolute value on a time line, regardless of time zone or culture. It refers to the absolute value of 00:00:00, January 1, 2001, in UTC.

An important concept here is that when we describe time in programming languages, we use the absolute value of a time line as a reference point, and the reference point plus an offset (in seconds or milliseconds, microseconds, or nanoseconds) to describe other points in time.

With this in mind, it becomes clear to look at some of the API calls to NSDate, such as:

NSDate* date = [NSDate date];
NSLog(@"current date interval: %f", [date timeIntervalSinceReferenceDate]);Copy the code

TimeIntervalSinceReferenceDate returns the distance from the reference time offset, the offset value is 502945767 seconds, 502945767/86400/365 = 15.9483056507, 86400 is contained the number of seconds a day, 365 is roughly the number of days in a year, and 15.94 is, of course, the number of years, which works out to be exactly the difference between this point and 2001.

For example, if the current time is 11:29 am Beijing time as I write this article, look at the following output:

NSDate* date = [NSDate date];
NSLog(@"current date: %@", date);Copy the code

Current Date: 2016-12-09 03:29:09 +0000 NSDate outputs absolute UTC time, while Beijing time is UTC+8. The output above is +8 hours, which is exactly my current time.

NSDate is city – and culture-independent, so to display the time in a specific format, we need the help of NSDateFormatter and NSTimeZone.

Another important point about NSDate is that NSDate is controlled by the mobile phone system time. That is, when you change the time display on your phone, the NSDate output for the current time changes. When we were making our App, we knew that NSDate wasn’t reliable because the user might change its value.

CFAbsoluteTimeGetCurrent()

The official definition is as follows:

Absolute time is measured in seconds relative to the absolute reference date of Jan 1 2001 00:00:00 GMT. A positive value represents a date after the reference date, a negative value represents a date before it. For example, the absolute time -32940326 is equivalent to December 16th, 1999 at 17:54:34. Repeated calls to this function do not guarantee monotonically increasing results. The system time may decrease due to synchronization with external time references or due to an explicit user change of the clock.

CFAbsoluteTimeGetCurrent() is very similar to NSDate except that the reference point is: the absolute value of time at 00:00:00, January 1, 2001 as GMT.

CFAbsoluteTimeGetCurrent() also changes with the system time of the current device and may be modified by the user.

gettimeofday

The API can also return a value describing the current time as follows:

struct timeval now;
struct timezone tz;
gettimeofday(&now, &tz);
NSLog(@"gettimeofday: %ld", now.tv_sec);Copy the code

The value obtained using getTimeofday is Unix time. What is Unix time?

Unix time is the number of seconds that the current time is offset from the reference point based on 00:00:00, January 1, 1970 UTC. The value returned by the API is 1481266031, which means 1481266031 seconds elapsed since 00:00:00, January 1, 1970 UTC.

Unix time is also a commonly used time standard. On the Mac terminal, you can use the following command to convert the time to readable time:

date -r 1481266031Copy the code

In fact NSDate also has an API that returns Unix time:

NSDate* date = [NSDate date];
NSLog(@"timeIntervalSince1970: %f", [date timeIntervalSince1970]);Copy the code

Gettimeofday, like NSDate and CFAbsoluteTimeGetCurrent(), is affected by the system timeof the current device. It’s just a different time reference point. We use Unix time when communicating with the server.

mach_absolute_time()

Mach_absolute_time () may be used less often, but it’s a very important concept.

As mentioned earlier, we needed to find a uniform property value to describe time, and we had one on our iPhone: the CPU’s clock cycles. The tick value can be used to describe the time, and mach_Absolute_time () returns the number of ticks the CPU has already run. This tick number can be converted into seconds or nanoseconds after a certain amount of conversion, which is directly related to time.

The tick count, however, restarts every time the phone restarts and pauses when the iPhone’s lock screen goes to sleep.

Mach_absolute_time () is not affected by system time, only by device restart and hibernation behavior.

CACurrentMediaTime()

CACurrentMediaTime() may be in contact with more students, let’s take a look at the official definition:

/* Returns the current CoreAnimation absolute time. This is the result of * calling mach_absolute_time () and converting  the units to seconds. */CFTimeInterval CACurrentMediaTime (void)Copy the code

CACurrentMediaTime() is the result of converting the CPU tick number of mach_Absolute_time () above to seconds. The following code:

double mediaTime = CACurrentMediaTime();
 NSLog(@"CACurrentMediaTime: %f", mediaTime);Copy the code

Returns the total number of seconds the device has been running (not counting device hibernation) since startup. Another API can return the same value:

NSTimeInterval systemUptime = [[NSProcessInfo processInfo] systemUptime];
 NSLog(@"systemUptime: %f", systemUptime);Copy the code

CACurrentMediaTime() is also not affected by system time, only by device restart and hibernation behavior.

sysctl

IOS also records the last time the device was rebooted. It can be obtained through the following API calls:

#include - (long)bootTime { #define MIB_SIZE 2 int mib[MIB_SIZE]; size_t size; struct timeval boottime; mib[0] = CTL_KERN; mib[1] = KERN_BOOTTIME; size = sizeof(boottime); if (sysctl(mib, MIB_SIZE, &boottime, &size, NULL, 0) ! = -1) { return boottime.tv_sec; } return 0; }Copy the code

The value returned is the Unix time of the last device restart.

The value returned by this API is also affected by the system time, and if the user changes the time, the value will change as well.

With these various ways of gaining time, let’s look at some specific applications in some scenarios.

Scenario one, time measurement

When we do performance optimization, we often need to record the execution time of a method, which will inevitably use some of the methods mentioned above to obtain the time.

When benchmarking method execution times, our method to capture time has two requirements: first, intensive reading is high, but the API itself consumes almost no CPU time.

Client performance tuning is generally done for mainthread fluency, and we know that UI threads will drop frames if they block for more than 16.7ms, so the time we care about is actually in the milliseconds (ms) level. When we write client code, we are basically in the ms dimension. If a method has a loss of 0.1ms, we can assume that the method is safe for fluency. If we frequently see methods that exceed 1ms or several ms, the main thread will have a higher chance of stoning.

For example, an NSData API call returns 0.000004s of intensive reading, or 4 microseconds, and CACurrentMediaTime() returns microseconds of intensive reading. However, there is a view that NSDate is a class encapsulation, and the loss caused by OOP high-level language itself may affect the actual results. When making benchmark, IT is not as accurate as C function call. In order to verify this statement, I write a simple test code:

int testCount = 10000;
double avgCost = 0;
for (int i = 0; i < testCount; i ++) {   
    NSDate* begin = [NSDate date];    
    NSLog(@"a meaningless log");
    avgCost += -[begin timeIntervalSinceNow];
}
NSLog(@"benchmark with NSDate: %f", avgCost/testCount);

avgCost = 0;
for (int i = 0; i < testCount; i ++) {   
    double startTime = CACurrentMediaTime();    
    NSLog(@"a meaningless log");    
    double endTime = CACurrentMediaTime();
    avgCost += (endTime - startTime);
}
NSLog(@"benchmark with CACurrentMediaTime: %f", avgCost/testCount);Copy the code

The output is:

Benchmark with NSDate: 0.000046 Benchmark with CACurrentMediaTime: 0.000037Copy the code

It can be seen that the loss difference between CACurrentMediaTime and NSDate codes is within a few microseconds, and the dimension of UI performance optimization is at the millisecond level. The difference of a few microseconds will not affect our final judgment at all. So it’s totally possible to use NSDate as a benchmark. Here are two macros I use commonly:

#define TICK   NSDate *startTime = [NSDate date]#define TOCK   NSLog(@"Time Cost: %f", -[startTime timeIntervalSinceNow])Copy the code

Scenario 2: Time synchronization between the client and server

This is also the scene we often encounter, for example, e-commerce apps start to buy things at midnight, such as countdown of commodity purchase limit, etc. In this scenario, we need to keep the client time consistent with the server. Most importantly, we need to prevent users from changing the system time through disconnection to affect the client logic.

It is common practice to add the Server time to some common Server interfaces. Every time the interface is called, the client synchronizes with the Server time and records it. But how to prevent users from modifying it?

The above mentioned NSDate, CFAbsoluteTimeGetCurrent, getTimeofDay and sySCtl all follow the change of system time. Although mach_Absolute_time and CACurrentMediaTime are based on CPU clock count, It is not affected by system time, but can be affected by sleep and restart. It doesn’t look right, so here’s my personal approach.

ServerTime (Unix time) and lastSyncLocalTime of the current client are recorded each time. When calculating the local time later, curLocalTime is first used to calculate the offset. Add serverTime to get the time:

uint64_t realLocalTime = 0; if (serverTime ! = 0 && lastSyncLocalTime ! = 0) { realLocalTime = serverTime + (curLocalTime - lastSyncLocalTime); }else { realLocalTime = [[NSDate date] timeIntervalSince1970]*1000; }Copy the code

If it never synchronizes with the server’s time, it will have to use the local system time, which hardly matters, indicating that the client hasn’t started using it yet.

The key is that if you get the local time, you can use a trick to get how long the system is currently running, and use the system running time to record the current client time:

//get system uptime since last boot- (NSTimeInterval)uptime
{    
    struct timeval boottime;    
    int mib[2] = {CTL_KERN, KERN_BOOTTIME};
    size_t size = sizeof(boottime);    
     struct timeval now;   
     struct timezone tz;
    gettimeofday(&now, &tz);   
     double uptime = -1;   
     if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0)
    {
        uptime = now.tv_sec - boottime.tv_sec;
        uptime += (double)(now.tv_usec - boottime.tv_usec) / 1000000.0;
    }   
     return uptime;
}Copy the code

Both getTimeofDay and sysctl are affected by system time, but the values obtained by subtracting them are independent of system time. This prevents the user from changing the time. Of course, if the user shuts down and starts up again after a period of time, the obtained time will be slower than the server time. In real scenarios, the time slower than the server time usually has little impact. We generally worry that the client time is faster than the server time.

Do more time synchronization with the Server, and put the critical time verification logic on the Server side, there will be no unexpected bugs.

conclusion

This concludes the logic of time processing. The key lies in our understanding of time itself, the various ways of expressing time, and the principles behind it so that we can choose the appropriate tools.

Welcome to follow the public account: MrPeakTech