閑話扯淡
好久沒更新文章(估算有小半年了),之前上半年有段時(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捞奕,如下截圖:
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代碼如下:
里面再往里看能看到tv_sec是通過boottime_sec獲取的艰争,
boottime_sec的代碼在/bsd/kern/kern_time.c中。
里面的clock_get_boottime_nanotime函數(shù)是在/osfmk/kern/clock.c中實(shí)現(xiàn)的桂对。
這里插一句甩卓,如果大家去查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);
</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)資料:
stackoverflow:有人問這個(gè)
mach_absolute_time
的真正含義马胧,因?yàn)樗l(fā)現(xiàn)自己的設(shè)備并沒有重啟,但是基于mach_absolute_time
算出來的時(shí)間衔峰,自己的設(shè)備在某一時(shí)刻重啟過佩脊,所以對(duì)這個(gè)東西"一臉懵逼"...
http://stackoverflow.com/questions/1450737/...Apple 官方對(duì)某個(gè)開發(fā)者的提問What time units does it use?進(jìn)行的回復(fù).
https://developer.apple.com/library/content/qa/qa1398/...某位大牛的博客文章: mach_absolute_time使用
http://www.devdiv.com/mach_absolute_time...