iOS 時間校準解決方案

EnezeeChurc 7年前發布 | 8K 次閱讀 iOS開發 移動開發

背景

在 iOS 開發中,凡是用到系統時間的,都要考慮一個問題:對時。有些業務是無需對時,或可以以用戶時間為準的,比如動畫用到的時間、一些日程類應用等。但電商相關的業務大都不能直接使用設備上的時間,而是需要跟服務器校準后的時間,例如:

  • 區間判斷:一些優惠促銷活動需要在 app 端判斷當前是否在活動期間內。如果用戶設備時間不準,會給用戶錯誤的信息,導致投訴。
  • 倒計時:各種秒殺、限時促銷、未支付訂單的失效等的倒計時。如果用戶設備時間不準,會帶來倒計時結束后刷新頁面,狀態沒變化的問題。可以測試一下電商大廠的 app,任意撥表之后倒計時仍是正確的。
  • 同步:如有數據同步的需求,設備時間不準會造成不能正確判斷數據的新舊關系,可能會讓舊數據覆蓋新數據,造成數據丟失。
  • 請求時間戳:對于分頁的數據,為了防止新插入的數據導致翻頁時數據錯亂,一個常見的解決方案是請求列表時加上時間戳的參數,后臺過濾只顯示時間戳之后的數據。如果用戶設備表慢了,就會顯示不出最新的數據,導致新發布內容在列表不出現的情況。

可以看出,對時這個需求是非常普遍的。不過實現起來并不難,在這里分享一下我們的經驗。

解決方案

之所以叫解決方案,是因為這個功能不單是 app 端加幾行代碼,而是前后端配合完成的。大概思路如下:

  1. 后端需要做的:每一個網絡請求的返回數據都要帶有服務器當前時間戳
  2. app 端的網絡框架在網絡請求的公共回調處取出時間戳
  3. 將服務器時間與本地時間的差值緩存到本地
  4. 需要使用時間時,使用本地時間和緩存的時間差,算出相應的服務器時間

網絡請求回調

服務器的時間戳可以加在 response body 里作為公共字段。在我的項目里,因為有少量 get 請求,所以放在了 response header 里。代碼類似如下:

+ (void)handleSuccessResponse:(id)responseObjectoperation:(AFHTTPRequestOperation *)operationresponseType:(Class)responseClasssuccess:(void (^)(id))successBlockfailure:(void (^)(NSError *))failureBlock {
    long long timestamp = [[operation.response.allHeaderFieldsobjectForKey:@"Response-Timestamp"] longLongValue];
    [HAMDateTimeUtilsupdateServerTime:timestamp];
}

每次網絡請求成功時更新時間差的緩存。

一個小的注意點是,處理 timestamp 最好始終用 long long 類型。因為 timestamp 傳統上是以毫秒為單位的(雖然在 iOS 這個奇葩系統里 NSTimeInteval 是以秒為單位),在 32 位系統上 long 和 NSInteger 都存不下,會溢出。當然,現在 32 位系統的設備已經不常見了。

時間差的緩存

在更新緩存時,把服務器時間與本地當前的時間差保存在單例里。

HAMDateTimeUtils.m

- (void)updateServerTime:(long long)timestamp {
    NSTimeIntervaltimeInteval = timestamp / 1000.0 - [[NSDatedate] timeIntervalSince1970];
    [self sharedInstance].timeIntevalDifference = timeInteval;
}

提供校準過的時間

需要使用時間時,根據當前時間和緩存過的時間差,計算校準后的時間:

HAMDateTimeUtils.m

+ (NSDate)currentTime {
    NSDate serverDate = [NSDatedateWithTimeIntervalSinceNow:[self sharedInstance].timeIntevalDifference];
    return serverDate;
}
 
// 以毫秒為單位

  • (long long)currentTimeStamp {     NSTimeIntervallocalTime = [[NSDatedate] timeIntervalSince1970];     NSTimeIntervaltimeDifference = [WNYDateTimeUtilssharedInstance].timeIntevalDifference;       return (long long)((localTimeStamp + timeDifference) * 1000); } </code></pre>

    使用時只需調用 [HAMDateTimeUtils currentTime] 或 [HAMDateTimeUtils currentTimeStamp] 即可。

    討論

    • Q:這樣得出的時間準確嗎?
      A:會有一定誤差。原因在于,服務器返回的時間戳是從服務器開始返回數據的時間,到客戶端接收時會有一點延遲。不過對于我們的后臺,這個延遲一般 如果對準確性要求更高,可以考慮使用專門的對時接口,不知道國家天文臺有沒有……
      另外,這種對時的方案只是用于優化 UI 層面的顯示,不能防止用戶惡意的篡改。要始終記住客戶端的時間戳是不可信的,后端業務凡是使用時間都務必用服務器的時間。
    • Q:緩存的時候,為什么只存在單例里,不持久化存儲?
      A:這個我也考慮過,主要是覺得再次啟動的時候,時間差可能會發生變化,感覺持久化沒有太大的必要。如果覺得有必要的話,也可以在 userDefault 里存一份,啟動時取出來即可。

     

    來自:http://ios.jobbole.com/91410/

     

 本文由用戶 EnezeeChurc 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!