[效能優化]DateFormatter深度優化探索

Not_Found發表於2018-12-20

前言

在iOS開發中,對日期進行格式化處理通常有三個步驟:

  • 建立DateFormatter物件
  • 設定日期格式
  • 使用DateFormatter物件對日期進行處理

在上篇文章《DateFormatter效能優化》中,我們通過建立單例物件的方式對建立DateFormatter物件,設定日期格式兩個步驟進行了快取,將方法耗時降低為不快取的方案的10%左右,但是這種優化方法受制於DateFormatter的幾個系統方法的執行效率,本身具有一定的侷限性。之前在一些文章中,也看到了使用C語言的

size_t  strftime_l(char * __restrict, size_t, const char * __restrict,  const struct tm * __restrict, locale_t)  __DARWIN_ALIAS(strftime_l) __strftimelike(3);
複製程式碼

函式對日期格式化進行處理,所以本文將對以下幾種情況的方法耗時進行評測:

  • 使用Objective-C,不快取DateFormatter物件
  • 使用Objective-C,快取DateFormatter物件
  • 使用Objective-C,呼叫strftime_l做日期處理
  • 使用Swift,不快取DateFormatter物件
  • 使用Swift,快取DateFormatter物件
  • 使用Swift,呼叫strftime_l做日期處理

Objective-C的三種情況下的程式碼

//不快取DateFormatter物件-(void)testDateFormatterInOCWithoutCache:(NSInteger)times { 
NSString *string = @"";
NSDate *date;
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
for (int i=0;
i<
times;
i++) {
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
[dateFormatter setDateFormat:@"yyyy年MM月dd日HH時mm分ss秒"];
date = [NSDate dateWithTimeIntervalSince1970:(1545308405 + i)];
string = [dateFormatter stringFromDate:date];

} CFAbsoluteTime duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
NSLog(@"\n不快取DateFormatter物件的方案:\n計算%ld次\n耗時%f ms\n", (long)times, duration);

}//快取DateFormatter物件-(void)testDateFormatterInOCWithCache:(NSInteger)times {
NSString *string = @"";
NSDate *date;
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
for (int i=0;
i<
times;
i++) {
date = [NSDate dateWithTimeIntervalSince1970:(1545308405 + i)];
string = [[DateFormatterCache shareInstance].formatterOne stringFromDate:date];

} CFAbsoluteTime duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
NSLog(@"\n快取DateFormatter物件的方案:\n計算%ld次\n耗時%f ms\n", (long)times, duration);

}//使用C語言來做日期處理-(void)testDateFormatterInC:(NSInteger)times {
NSString *string = @"";
NSDate *date;
time_t timeInterval;
char buffer[80];
CFAbsoluteTime startTime = CFAbsoluteTimeGetCurrent();
for (int i=0;
i<
times;
i++) {
date = [NSDate dateWithTimeIntervalSince1970:(1545308405 + i)];
timeInterval = [date timeIntervalSince1970];
strftime(buffer, sizeof(buffer), "%Y年%m月%d日%H時%M分%S秒", localtime(&
timeInterval));
string = [NSString stringWithCString:buffer encoding:NSUTF8StringEncoding];

} CFAbsoluteTime duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
NSLog(@"==%@", string);
NSLog(@"\n使用C語言的方案:\n計算%ld次\n耗時%f ms\n", (long)times, duration);

}複製程式碼

這裡對於我們們iOS開發的同學來說比較陌生的就是strftime_l(buffer, sizeof(buffer), "%Y年%m月%d日%H時%M分%S秒", localtime(&
timeInterval), NULL);
這這行程式碼的呼叫,strftime_l函式接受四個引數,第一個引數buffer是C語言中字元陣列用於儲存日期格式化後的字串,第二個引數是寫入buffer陣列的最大值,如果格式化的字串大於這個值,那麼只會取字串的的一部分,第三個引數”%Y年%m月%d日%H時%M分%S秒”是日期格式,第四個引數localtime(&
timeInterval)是指向使用當地時區對時間戳處理得到tm型別結構體的指標

附上tm結構體:

struct tm { 
int tm_sec;
/* seconds after the minute [0-60] */ int tm_min;
/* minutes after the hour [0-59] */ int tm_hour;
/* hours since midnight [0-23] */ int tm_mday;
/* day of the month [1-31] */ int tm_mon;
/* months since January [0-11] */ int tm_year;
/* years since 1900 */ int tm_wday;
/* days since Sunday [0-6] */ int tm_yday;
/* days since January 1 [0-365] */ int tm_isdst;
/* Daylight Savings Time flag */ long tm_gmtoff;
/* offset from UTC in seconds */ char *tm_zone;
/* timezone abbreviation */
};
複製程式碼

Swift三種情況下的程式碼

    //不進行快取    func testInOldWay(_ times: Int) { 
var string = "" var date = Date.init() let startTime = CFAbsoluteTimeGetCurrent();
for i in 0..<
times {
let formatter = DateFormatter() formatter.dateFormat = "yyyy年MM月dd日HH時mm分ss秒" date = Date.init(timeIntervalSince1970: TimeInterval(1545308405 + i)) string = formatter.string(from: date)
} let duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
print("使用oldWay計算\n\(times)次,總耗時\n\(duration) ms\n")
} //進行快取 func testInNewWay(_ times: Int) {
var string = "" var date = Date.init() let startTime = CFAbsoluteTimeGetCurrent();
for i in 0..<
times {
date = Date.init(timeIntervalSince1970: TimeInterval(1545308405 + i)) string = DateFormatterCache.shared.dateFormatterOne.string(from: date)
} let duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
print("使用快取Formatter的方案計算\n\(times)次,總耗時\n\(duration) ms\n")
} //使用C語言來做日期處理 func testFormatterInC(_ times: Int) {
var date = Date.init() var dateString = "" var buffer = [Int8](repeating: 0, count: 100) var time = time_t(date.timeIntervalSince1970) let format = "%Y年%m月%d日%H時%M分%S秒" let startTime = CFAbsoluteTimeGetCurrent();
for i in 0..<
times {
date = Date.init(timeIntervalSince1970: TimeInterval(1545308405 + i)) time = time_t(date.timeIntervalSince1970) strftime(&
buffer, buffer.count, format, localtime(&
time)) dateString = String.init(cString: buffer, encoding: String.Encoding.utf8) ?? ""
} let duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
print("使用C語言的方案計算\n\(times)次,總耗時\n\(duration) ms\n") print(dateString)
}複製程式碼

iOS 12.1 iPhone 7

測試結果:

[效能優化]DateFormatter深度優化探索

測試結果:

在Objective-C中,不使用快取,使用快取,使用C語言函式處理的耗時比約為100:10.7:3.5

在Swift中,不使用快取,使用快取,使用C語言函式處理的耗時比約為100:11.7:6.6

Swift在使用DateFormatter進行處理時,不論是快取的方案還是不快取的方案,跟使用Objective-C的耗時基本一致,而在Swift中使用C語言的函式來做日期處理時,時間約為使用Objective-C的兩倍,而且當只做一次日期處理時,由於涉及到一些初始資源的初始化,所以看上去比後面執行10次的時間還多

最後

如果專案是Objective-C的專案,我覺得可以採用這種C語言的strftime來做日期處理,能將時間降低為快取NSDateFormatter的方案的33%左右,如果是Swift專案,呼叫C語言函式的效率沒有在Objective-C專案中那麼高,雖然能將時間降低為快取NSDateFormatter的方案的56%左右,但是在Swift中使用C語言的函式存在一定的風險,在這裡風險之一就是time = time_t(date.timeIntervalSince1970)這行程式碼返回的值是time_t型別,time_t型別的定義如下:

public typealias time_t = __darwin_time_tpublic typealias __darwin_time_t = Int /* time() */複製程式碼

time_t其實就是Int,當Swift專案執行在32位裝置(也就是iphone 5,iphone 5C)上時,Int型別是32位的,最大值為2147483647,如果這是一個時間戳的值,轉換為正常時間是2038-01-19 11:14:07,也就是處理的時間是未來的日期,2038年以後的話,會出現數值溢位。

Demo在這裡:github.com/577528249/S…

參考資料:

forums.developer.apple.com/thread/2905…stackoverflow.com/questions/2…

PS:

最近加了一些iOS開發相關的QQ群和微信群,但是感覺都比較水,裡面對於技術的討論比較少,所以自己建了一個iOS開發進階討論群,歡迎對技術有熱情的同學掃碼加入,加入以後你可以得到:

1.技術方案的討論,會有在大廠工作的高階開發工程師儘可能抽出時間給大家解答問題

2.每週定期會寫一些文章,並且轉發到群裡,大家一起討論,也鼓勵加入的同學積極得寫技術文章,提升自己的技術

3.如果有想進大廠的同學,裡面的高階開發工程師也可以給大家內推,並且針對性得給出一些面試建議

群已經滿100人了,想要加群的小夥伴們可以掃碼加這個微信,備註:“加群+暱稱”,拉你進群,謝謝了

[效能優化]DateFormatter深度優化探索

來源:https://juejin.im/post/5c1b69dde51d45199c387a73

相關文章