IOS 時間處置
做App防止不了要和時間打交道,關於時間的處置,外面有不少門道,遠不是一行API調用,獲取以後零碎時間這麼復雜。我們需求理解與時間相關的各種API之間的差異,再因場景而異去設計相應的機制。
時間的方式
在開端深化討論之前,我們需求確信一個前提:時間是線性的。即恣意一個時辰,這個地球上只要一個相對時間值存在,只不過由於時區或許文明的差別,處於同一時空的我們對同一時間的表述或許了解不同。這個看似復雜明了的道理,是我們了解各種與時間相關的復雜概念的基石。好像UTF-8和UTF-16其實都是Unicode一樣,北京的20:00和東京的21:00其實是同一個相對的時間值。
GMT
人類關於時間的了解還很無限,但我們至多能確定一點:時間的變化是勻速的。時間行進的速度是平均的,不會忽快忽慢,所以為了描繪時間,我們也需求找到一個值,它的變化也是以平均的速度向前變化的。
說出來你能夠不信,我們人類為了尋覓這個參考值,來准確描繪以後的時間值,都閱歷了漫長歲月的探究。你可以嘗試考慮下,生活中有什麼事物是隨著時間平均變化的,它具有的數值屬性,會隨著時間處於相對的勻速變化形態。
後人發現低頭看太陽是個好方法,太陽總是按規律的“早起晚落”,而且“亘古不變”,可以用太陽在一天當中所處的地位來描繪以後的時間。後來不同地域的文明需求交流,你這裡太陽正地面照,我這能夠曾經下山了,所以需求有一個公共的大家都認可的中央,以這個中央太陽的地位來做參考著,溝通起來就會方便很多。最後選擇的是英國倫敦的格林尼治地理台所在地,以格林尼治的時間作為公共時間,也就是我們所說的GMT時間(Greenwich Mean Time)。
UTC
太陽所處的地位變化跟地球的自轉相關,過來人們以為地球自轉的速率是恆定的,但在1960年這一認知被推翻了,人們發現地球自轉的速率正變得越來越慢,而時間行進的速率還是恆定的,所以UTC不再被以為可以用來精准的描繪時間了。
我們需求持續尋覓一個勻速行進的值。低頭看天是我們從微觀方向去尋覓答案,科技的開展讓我們在微觀方面獲得了更深的看法,於是有聰明人依據微觀粒子原子的物理屬性,樹立了原子鐘,以這種原子鐘來權衡時間的變化,原子鐘50億年才會誤差1秒,這種精讀曾經遠勝於GMT了。這個原子鐘所反映的時間,也就是我們如今所運用的UTC(Coordinated Universal Time )規范時間。
接上去我們看下IOS裡,五花八門的記載時間的方式。
NSDate
NSDate是我們平常運用較多的一個類,先看下它的定義:
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).
NSDate對象描繪的是時間線上的一個相對的值,和時區和文明有關,它參考的值是:以UTC為規范的,2001年一月一日00:00:00這一刻的時間相對值。
這裡有個概念很重要,我們用編程言語描繪時間的時分,都是以一個時間線上的相對值為參考點,參考點再加上偏移量(以秒或許毫秒,微秒,納秒為單位)來描繪另外的時間點。
了解了這一點,再看NSDate的一些API調用就十分清楚了,比方:
NSDate* date = [NSDate date]; NSLog(@"current date interval: %f", [date timeIntervalSinceReferenceDate]);
timeIntervalSinceReferenceDate前往的是間隔參考時間的偏移量,這個偏移量的值為502945767秒,502945767/86400/365=15.9483056507,86400是一天所包括的秒數,365大致是一年的天數,15.94當然就是年數了,算出來剛好是此刻間隔2001年的差值。
又比方,此刻我寫文章的時分,以後時間為北京時間上午11:29,看看上面代碼的輸入:
NSDate* date = [NSDate date]; NSLog(@"current date: %@", date);
current date: 2016-12-09 03:29:09 +0000。可見NSDate輸入的是相對的UTC時間,而北京時間的時區為UTC+8,下面的輸入+8個小時,剛好就是我以後的時間了。
NSDate和郊區和文明有關,所以要展現詳細格式的時間,我們需求NSDateFormatter和NSTimeZone的輔佐。
另外關於NSDate最重要的一點是:NSDate是受手機零碎時間控制的。也就是說,當你修正了手機上的時間顯示,NSDate獲取以後時間的輸入也會隨之改動。在我們做App的時分,明白這一點,就知道NSDate並不牢靠,由於用戶能夠會修正它的值。
CFAbsoluteTimeGetCurrent()
官方定義如下:
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()的概念和NSDate十分類似,只不過參考點是:以GMT為規范的,2001年一月一日00:00:00這一刻的時間相對值。
異樣CFAbsoluteTimeGetCurrent()也會跟著以後設備的零碎時間一同變化,也能夠會被用戶修正。
gettimeofday
這個API也能前往一個描繪以後時間的值,代碼如下:
struct timeval now; struct timezone tz; gettimeofday(&now, &tz); NSLog(@"gettimeofday: %ld", now.tv_sec);
運用gettimeofday取得的值是Unix time。Unix time又是什麼呢?
Unix time是以UTC 1970年1月1號 00:00:00為基准時間,以後時間間隔基准點偏移的秒數。上述API前往的值是1481266031,表示以後時間間隔UTC 1970年1月1號 00:00:00一共過了1481266031秒。
Unix time也是平常我們運用較多的一個時間規范,在Mac的終端可以經過以下命令轉換成可閱讀的時間:
date -r 1481266031
實踐上NSDate也有一個API能前往Unix time:
NSDate* date = [NSDate date]; NSLog(@"timeIntervalSince1970: %f", [date timeIntervalSince1970]);
gettimeofday和NSDate,CFAbsoluteTimeGetCurrent()一樣,都是受以後設備的零碎時間影響。只不過是參考的時間基准點不一樣而已。我們和服務器通訊的時分普通運用Unix time。
mach_absolute_time()
mach_absolute_time()能夠用到的同窗比擬少,但這個概念十分重要。
後面提到我們需求找到一個平均變化的屬性值來描繪時間,而在我們的iPhone上剛好有一個這樣的值存在,就是CPU的時鐘周期數(ticks)。這個tick的數值可以用來描繪時間,而mach_absolute_time()前往的就是CPU曾經運轉的tick的數量。將這個tick數經過一定的轉換就可以變成秒數,或許納秒數,這樣就和時間直接關聯了。
不過這個tick數,在每次手機重啟之後,會重新開端計數,而且iPhone鎖屏進入休眠之後tick也會暫停計數。
mach_absolute_time()不會受零碎時間影響,只受設備重啟和休眠行為影響。
CACurrentMediaTime()
CACurrentMediaTime()能夠接觸到的同窗會多一些,先看下官方定義:
/* Returns the current CoreAnimation absolute time. This is the result of * calling mach_absolute_time () and converting the units to seconds. */ CFTimeInterval CACurrentMediaTime (void)
CACurrentMediaTime()就是將下面mach_absolute_time()的CPU tick數轉化成秒數的後果。以下代碼:
double mediaTime = CACurrentMediaTime(); NSLog(@"CACurrentMediaTime: %f", mediaTime);
前往的就是開機後設備一共運轉了(設備休眠不統計在內)多少秒,另一個API也能前往相反的值:
NSTimeInterval systemUptime = [[NSProcessInfo processInfo] systemUptime]; NSLog(@"systemUptime: %f", systemUptime);
CACurrentMediaTime()也不會受零碎時間影響,只受設備重啟和休眠行為影響。
sysctl
IOS零碎還記載了上次設備重啟的時間。可以經過如下API調用獲取:
#include <sys/sysctl.h> - (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; }
前往的值是上次設備重啟的Unix time。
這個API前往的值也會受零碎時間影響,用戶假如修正時間,值也會隨著變化。
有了以上獲取時間的各種手腕,我們再來看看一些場景之下的詳細使用。
場景一,時間測量
我們做功能優化的時分,常常需求對某個辦法執行的時間做記載,就必定會用到下面提到的一些獲取時間的辦法。
在做辦法執行時間的benchmark的時分,我們獲取時間的辦法要滿足兩個要求,一是精讀要高,而是API自身簡直不耗CPU時間。
客戶端做功能優化普通是為了主線程的流利性,而我們知道UI線程假如遇到超越16.7ms的阻塞,就會呈現掉幀景象,所以我們關注的時間的精讀實踐上是在毫秒(ms)級別。我們寫客戶端代碼的時分,根本上都是處於ms這一維度,假如一個辦法損耗是0.1ms,我們可以以為這個辦法關於流利性來說是平安的,假如常常看到超越1ms或許幾個ms的辦法,主線程呈現卡頓的幾率就會變高。
下面幾種獲取時間的方式精讀上都是足夠的,比方一個NSDateAPI調用前往的精讀是0.000004 S,也就是4微秒,CACurrentMediaTime()前往的精讀也在微秒級別,精讀上都契合要求。不過有一種看法,以為NSDate屬於類的封裝,OOP初級言語自身所帶來的損耗能夠會影響最後的實踐後果,在做benchmark的時分不如C函數調用精准,為了驗證這一說法,我寫了一段復雜的測試代碼:
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);
輸入後果為:
benchmark with NSDate: 0.000046 benchmark with CACurrentMediaTime: 0.000037
可以看出CACurrentMediaTime與NSDate代碼自身的損耗差別在幾微秒,而我們做UI功能優化的維度在毫秒級別,幾個微秒的差別完全不會影響我們最後的判別後果。所以運用NSDate做benchmark完全是可行的,以下是我常用的兩個宏:
#define TICK NSDate *startTime = [NSDate date] #define TOCK NSLog(@"Time Cost: %f", -[startTime timeIntervalSinceNow])
場景二:客戶端和服務器之間的時間同步
這也是我們常常遇到的場景,比方電商類App到零點的時分開端搶購,比方商品限購倒計時等等,這種場景下需求我們將客戶端的時間與服務器堅持分歧,最重要的是,要避免用戶經過斷網修正零碎時間,來影響客戶端的邏輯。
比擬普遍的做法是,在一些常用的Server接口外面帶上服務器時間,每調用一次接口,客戶端就和服務器時間做一次同步並記載上去,但問題是如何避免用戶修正呢?
下面提到的NSDate,CFAbsoluteTimeGetCurrent,gettimeofday,sysctl都是跟隨零碎時間變化的,mach_absolute_time和CACurrentMediaTime雖然是根據CPU時鐘數,不受零碎時間影響,但在休眠和重啟的時分還是會被影響。看上去都不太合適,這裡引見下我團體的做法。
首先還是會依賴於接口和服務器時間做同步,每次同步記載一個serverTime(Unix time),同時記載以後客戶端的時間值lastSyncLocalTime,到之後算本地時間的時分先取curLocalTime,算出偏移量,再加上serverTime就得出時間了:
uint64_t realLocalTime = 0; if (serverTime != 0 && lastSyncLocalTime != 0) { realLocalTime = serverTime + (curLocalTime - lastSyncLocalTime); }else { realLocalTime = [[NSDate date] timeIntervalSince1970]*1000; }
假如歷來沒和服務器時間同步過,就只能取本地的零碎時間了,這種狀況簡直也沒什麼影響,闡明客戶端還沒開端用過。
關鍵在於假如獲取本地的時間,可以用一個小技巧來獲取零碎以後運轉了多長時間,用零碎的運轉時間來記載以後客戶端的時間:
//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; }
gettimeofday和sysctl都會受零碎時間影響,但他們二者做一個減法所得的值,就和零碎時間有關了。這樣就可以防止用戶修正時間了。當然用戶假如關機,過段時間再開機,會招致我們獲取到的時間慢與服務器時間,真實場景中,慢於服務器時間往往影響較小,我們普通擔憂的是客戶端時間快於服務器時間。
多和服務器做時間同步,再把關鍵的時間校驗邏輯放在Server端,就不會呈現什麼不測的bug了。
總結
關於時間處置的邏輯就總結到這裡了,關鍵還在於我們關於時間自身的了解,關於表達時間的各種方式的了解,了解面前的原理才干選擇適宜的工具。
感激閱讀,希望能協助到大家,謝謝大家對本站的支持!
【IOS 開發APP之關於時間處置詳細引見】的相關資料介紹到這裡,希望對您有所幫助! 提示:不會對讀者因本文所帶來的任何損失負責。如果您支持就請把本站添加至收藏夾哦!