[iOS] 如何獲取一個(gè)更為靠譜的當(dāng)前時(shí)間

閑話扯淡

好久沒更新文章(估算有小半年了),之前上半年有段時(shí)間處于換工作空檔期,比較閑法挨,于是更新了好多篇文章,后來一忙起來幅聘,全身心投入到業(yè)務(wù)需求開發(fā)中凡纳,基本就沒太多時(shí)間研究技術(shù)的東西了。

背景

這次發(fā)新版本的時(shí)候喊暖,改一個(gè)bug時(shí)遇到一個(gè)時(shí)間戳的問題惫企,想著好久沒動(dòng)筆了,就順道把這東西記下來吧陵叽。
順道立個(gè)flag:爭(zhēng)取在17年到來之前狞尔,從這次開始,能多總結(jié)積累一些技術(shù)點(diǎn)巩掺,多出幾篇文章偏序。

需求

這次的需求非常簡(jiǎn)單,對(duì)于大部分童鞋估計(jì)掃一眼題目就知道123了胖替,也不用再往后看了研儒,我這里只當(dāng)是個(gè)小技術(shù)點(diǎn)的記錄吧豫缨。

言歸正傳,作為移動(dòng)的App端朵,平時(shí)向服務(wù)端發(fā)的所有request請(qǐng)求好芭,作為基礎(chǔ)數(shù)據(jù),所有的request都應(yīng)該帶requestId和請(qǐng)求的時(shí)間戳以及設(shè)備信息冲呢,經(jīng)緯度信息等等舍败,這些應(yīng)該作為默認(rèn)選項(xiàng)。
關(guān)于時(shí)間戳敬拓,因?yàn)榭蛻舳说臅r(shí)間不是很靠譜(用戶隨時(shí)可能自己修改時(shí)間)邻薯,所以原則上,客戶端的時(shí)間僅僅作為參考乘凸。真正涉及DBA或是DB部門數(shù)據(jù)統(tǒng)計(jì)或計(jì)算的時(shí)候厕诡,必然已server端為準(zhǔn)。
這里想說明的是营勤,雖然條件有限灵嫌,如何在有限的條件下,我們?nèi)∫粋€(gè)盡量可以準(zhǔn)確點(diǎn)的時(shí)間呢冀偶,這樣當(dāng)用戶處于無網(wǎng)絡(luò)或信號(hào)差的場(chǎng)景時(shí)醒第,App需要執(zhí)行打點(diǎn)記錄或者某些進(jìn)行一些需要時(shí)間的行為動(dòng)作時(shí),可以更加準(zhǔn)確點(diǎn)进鸠。
前提是稠曼,用戶可以隨意修改時(shí)間,但是我們有木有辦法繞過這個(gè)尷尬的現(xiàn)實(shí)客年,想個(gè)更巧妙的實(shí)現(xiàn)方式呢霞幅?

實(shí)現(xiàn)

1. 思路

首先,我們平時(shí)取時(shí)間的函數(shù)如下:

//ObjC
NSDate *date = [NSDate date];
// swift      
var date = NSDate()

那么這個(gè)方法不靠譜的話量瓜,只能再往深入一點(diǎn)想司恳。
那就是直接獲取底層的時(shí)間,想到的就是內(nèi)核kernel的時(shí)間了绍傲。每次系統(tǒng)重啟后扔傅,會(huì)一直記錄啟動(dòng)至今的時(shí)間。

這個(gè)時(shí)間稱之為upTime烫饼,我們獲取server時(shí)間和uptime的差值timerOffset 猎塞,將這個(gè)值保存下來,后續(xù)每次發(fā)request請(qǐng)求的時(shí)候杠纵,基于timerOffset將upTime換算為上傳的真正時(shí)間值即可荠耽,然后定時(shí)或者按照一定周期和服務(wù)端同步,將timerOffset進(jìn)行修正比藻。
其實(shí)簡(jiǎn)單點(diǎn)就是數(shù)學(xué)的a-b=c,a=b+c的問題铝量,代碼如下:

self.timerOffset = serverTimer - upTime;
NSTimeInterval uploadTime = upTime + self.timerOffset;

Okey倘屹,結(jié)構(gòu)上來說還是比較清晰也比較簡(jiǎn)單的,唯一的難點(diǎn)在于獲取啟動(dòng)時(shí)間Uptime慢叨,我們一步一步往下走纽匙。

2. 獲取啟動(dòng)時(shí)間

2.1 ObjC/swift的方式獲取

系統(tǒng)進(jìn)程可以通過NSProcessInfo獲取,然后他很好的提供給我們了一個(gè)Uptime接口插爹,直接就可以獲取到啟動(dòng)時(shí)間哄辣,代碼如下:

//ObjC
NSTimeInterval systemUptime =[[NSProcessInfo processInfo] systemUptime];
//swift
var systemUptime = ProcessInfo().systemUptime;

這個(gè)獲取到的時(shí)間但是是秒请梢,打印一下赠尾,大概是這個(gè)樣子:

(lldb) po [[NSProcessInfo processInfo] systemUptime]
11473.798027249

然后,然后毅弧,就完事了气嫁? 好像我們的需求就這么達(dá)到了?
嗯够坐,至少看上去是這樣寸宵,然后,直接寫代碼元咙,git push了...
當(dāng)然梯影,理論上是這樣的,
但是作為一個(gè)有想法的coder庶香,怎么能就此打住呢甲棍,還是需要繼續(xù)探索啊...

2.2 通過C的方式獲取kernel_task的啟動(dòng)時(shí)間

2.2.1

time這個(gè)東西,不同于其他赶掖,原則上越精準(zhǔn)越好感猛,當(dāng)然,對(duì)于大部分App來說奢赂,是基本沒需求的陪白,但是對(duì)于學(xué)習(xí)和既能提升來說,還是可以再往深里考慮考慮的膳灶。
首先OC或者swift來說咱士,畢竟隔了一層編譯,效率上畢竟和C或者Java還是有那么點(diǎn)差距轧钓,所以需要考慮有沒有通過C的方式實(shí)現(xiàn)這個(gè)問題序厉。

2.2.2

既然我這么問了,答案自然是有的聋迎。

"就是獲取kernel_task的啟動(dòng)時(shí)間"

</br>

kernel_task是個(gè)系統(tǒng)級(jí)的task脂矫,用mac的人可能比較熟,很多人當(dāng)電腦比較燙或者風(fēng)扇轉(zhuǎn)的比較響的時(shí)候霉晕,看一下活動(dòng)監(jiān)視器庭再,就會(huì)看到kernel_task捞奕,如下截圖:


屏幕快照 2016-10-28 下午5.24.03.png

kernel_task包括多線程調(diào)度管理、虛擬內(nèi)存拄轻、系統(tǒng)IO颅围、線程之間通信等等。所以系統(tǒng)已啟動(dòng)恨搓,kernel_task就會(huì)跑起來院促,kernel_task運(yùn)行的時(shí)間,就可以作為啟動(dòng)時(shí)間來使用斧抱。

2.2.3

獲取kernel_task的啟動(dòng)時(shí)間常拓,我們需要用到一個(gè)函數(shù)sysctl, 首先看一下sysctl函數(shù)的定義:

int sysctl(int *, u_int, void *, size_t *, void *, size_t);
  • sysctl的第一參數(shù)是個(gè)數(shù)組辉浦,按照順序第一個(gè)元素指定本請(qǐng)求定向到內(nèi)核的哪個(gè)子系統(tǒng)弄抬,第二個(gè)及其后元素依次細(xì)化指定該系統(tǒng)的某個(gè)部分,類推...
  • 之后是數(shù)組的長(zhǎng)度u_int.
  • 后面的size_t *需要注意宪郊,當(dāng)sysctl被調(diào)用時(shí)掂恕,size指向的值指定該緩沖區(qū)的大小弛槐;函數(shù)返回時(shí)懊亡,該值給出內(nèi)核存放在該緩沖區(qū)中的數(shù)據(jù)量,如果這個(gè)緩沖不夠大,函數(shù)就返回ENOMEM錯(cuò)誤
  • sysctl函數(shù)的結(jié)果:成功:返回0乎串; 失數暝妗:返回-1

2.2.4

一言不合show me the code:

- (time_t)uptime
{
    struct timeval boottime;
    int mib[2] = {CTL_KERN, KERN_BOOTTIME};
    size_t size = sizeof(boottime);
    time_t now;
    time_t uptime = -1;

    (void)time(&now);

    if (sysctl(mib, 2, &boottime, &size, NULL, 0) != -1 && boottime.tv_sec != 0)
    {
        uptime = now - boottime.tv_sec;
    }
    return uptime;
}

具體sysctl的代碼,可以看/bsd/kern/kern_sysctl.c
其中的sysctl_boottime函數(shù)可以獲取boottime灌闺,sysctl_boottime代碼如下:

屏幕快照 2016-10-29 上午12.44.54.png

里面再往里看能看到tv_sec是通過boottime_sec獲取的艰争,
boottime_sec的代碼在/bsd/kern/kern_time.c中。

屏幕快照 2016-10-29 上午12.53.32.png

里面的clock_get_boottime_nanotime函數(shù)是在/osfmk/kern/clock.c中實(shí)現(xiàn)的桂对。
屏幕快照 2016-10-29 上午12.55.20.png

這里插一句甩卓,如果大家去查sysctl,會(huì)查到sysctl在iOS9以后不可用的一些信息蕉斜,這里我自己沒直接找到相關(guān)的文檔逾柿,但是我們這個(gè)時(shí)間的接口是可用的,iOS10親測(cè)正常宅此。唯一查到的相關(guān)信息是通過sysctl獲取進(jìn)程信息的那個(gè)路子被堵死了。
那個(gè)代碼如下弱匪,大家不要搞混淆了.

    int mib[4] = {CTL_KERN, KERN_PROC, KERN_PROC_ALL ,0};
    size_t miblen = 4;
    size_t size;
    int st = sysctl(mib, miblen, NULL, &size, NULL, 0);

想深入了解,看這里https://nabla...

</br>

2.3 還遺漏了一種方式mach_absolute_time

2.3.1

mach_absolute_time這個(gè)也是在查詢sysctl相關(guān)信息的時(shí)候哑诊,意外發(fā)現(xiàn)的.
它不會(huì)直接告訴你一個(gè)時(shí)間,你可以理解為CPU進(jìn)程依賴的一個(gè)函數(shù)镀裤,它會(huì)返回系統(tǒng)重啟到現(xiàn)在的一個(gè)時(shí)鐘“滴答”數(shù),這個(gè)值自然是不能直接用的缴饭,畢竟是硬件層概念使用的一個(gè)參數(shù)暑劝,但是我們經(jīng)過簡(jiǎn)單處理,就可以獲取所需要的啟動(dòng)時(shí)間。

2.3.2

show me the code:

#include <mach/mach_time.h>

int getUptimeInMilliseconds()
{
    const int64_t kOneMillion = 1000 * 1000;
    static mach_timebase_info_data_t s_timebase_info;

    if (s_timebase_info.denom == 0) {
        (void) mach_timebase_info(&s_timebase_info);
    }

    // mach_absolute_time() returns billionth of seconds,
    // so divide by one million to get milliseconds
    return (int)((mach_absolute_time() * s_timebase_info.numer) / (kOneMillion * s_timebase_info.denom));
}

2.3.3

但是這個(gè)方法使用的人不多卖丸,原因在于,如果系統(tǒng)處于休眠狀態(tài)猜嘱,那么這個(gè)值也是停止的衅枫,所以它本質(zhì)是進(jìn)程運(yùn)行的時(shí)鐘計(jì)數(shù)器(run()),而非整個(gè)系統(tǒng)的(run() + sleep())時(shí)鐘計(jì)數(shù)器朗伶。這樣嚴(yán)格來說,是完全不準(zhǔn)確的數(shù)據(jù)屠橄,不可用姐仅。
但具體相關(guān)我也沒深入研究過掏膏,有了解過的童鞋劳翰,可以評(píng)論或私信給我,指教我一下佳簸。

這個(gè)較為多的使用場(chǎng)景是作為耗時(shí)的測(cè)量函數(shù),比如在某個(gè)時(shí)間獲取mach_absolute_time作為startTime颖变,間隔一定時(shí)間之后再次獲取mach_absolute_time作為endTime生均,然后差值可以作為這個(gè)階段的耗時(shí),這樣的使用場(chǎng)景較多腥刹。

2.3.4

查到的相關(guān)資料:

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市垫卤,隨后出現(xiàn)的幾起案子威彰,更是在濱河造成了極大的恐慌,老刑警劉巖穴肘,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件歇盼,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡梢褐,警方通過查閱死者的電腦和手機(jī)旺遮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來盈咳,“玉大人耿眉,你說我怎么就攤上這事∮阆欤” “怎么了鸣剪?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我筐骇,道長(zhǎng)债鸡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任铛纬,我火速辦了婚禮厌均,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘告唆。我一直安慰自己棺弊,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布擒悬。 她就那樣靜靜地躺著模她,像睡著了一般。 火紅的嫁衣襯著肌膚如雪懂牧。 梳的紋絲不亂的頭發(fā)上侈净,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音僧凤,去河邊找鬼畜侦。 笑死,一個(gè)胖子當(dāng)著我的面吹牛拼弃,可吹牛的內(nèi)容都是我干的夏伊。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼吻氧,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了咏连?” 一聲冷哼從身側(cè)響起盯孙,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎祟滴,沒想到半個(gè)月后振惰,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡垄懂,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年骑晶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片草慧。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡桶蛔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出漫谷,到底是詐尸還是另有隱情仔雷,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站碟婆,受9級(jí)特大地震影響电抚,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜竖共,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一蝙叛、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧公给,春花似錦借帘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至匣沼,卻和暖如春狰挡,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背释涛。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工加叁, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人唇撬。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓它匕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親窖认。 傳聞我的和親對(duì)象是個(gè)殘疾皇子豫柬,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容

  • 目標(biāo)是開發(fā)一個(gè)SDK烧给,嵌入到APP里面,用來統(tǒng)計(jì)當(dāng)前APP的實(shí)時(shí)CPU喝噪、內(nèi)存等信息 2015.11.17 http...
    953e9bf34714閱讀 5,609評(píng)論 0 8
  • 如果你看完書中的所有例子础嫡,你很可能已經(jīng)做完你的實(shí)驗(yàn)和在已經(jīng)越獄的iPhone上的研究。因?yàn)楹驮S多人一樣酝惧,幾乎所有的...
    fishmai0閱讀 15,832評(píng)論 2 42
  • 1.NSLog(@"%@",[[UIDevice currentDevice] systemVersion]);/...
    Y像夢(mèng)一樣自由閱讀 10,609評(píng)論 3 12
  • 場(chǎng)景 我們經(jīng)常遇到這樣的場(chǎng)景榴鼎,比如電商類App到零點(diǎn)的時(shí)候開始搶購(gòu),比如商品限購(gòu)倒計(jì)時(shí)等等晚唇。這種場(chǎng)景下需要我們將客...
    zhangbo1992閱讀 4,687評(píng)論 10 39
  • 關(guān)鍵時(shí)刻巫财,第一時(shí)間送達(dá)! 問題種類 時(shí)間復(fù)雜度 在集合里數(shù)據(jù)量小的情況下時(shí)間復(fù)雜度對(duì)于性能的影響看起來微乎其微缺亮。但...
    C9090閱讀 882評(píng)論 0 1