1.以前在一些性能優化的文章《性能優化之NSDateFormatter 》中,看到有提到「建立DateFormatter開銷會比較大」,也有的文章《(多帖總結) iOS性能優化技巧》裏面說是「設置日期格式」這個方法較爲耗時,但實際上測試發現是「生成字符串」這個方法較爲耗時,因此我以爲能夠糾正一些這些說法html
let formatter = DateFormatter()//建立DateFormatter實例對象
formatter.dateFormat = "yyyy年MM月dd日"//設置日期格式
string = formatter.string(from: date)//生成字符串
複製代碼
2.不少同窗可能只是跟我以前同樣,只是知道這個方法比較耗時,可是對於進行緩存優化後的效果對比並不清楚,因此本身寫了一個小Demo,對優化先後進行一些性能測試,方便你們參考,也方便你們在項目中使用ios
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
testInOldWay(1)
testInNewWay(1)
testInOldWay(10)
testInNewWay(10)
testInOldWay(100)
testInNewWay(100)
testInOldWay(1000)
testInNewWay(1000)
testInOldWay(10000)
testInNewWay(10000)
testInOldWay(100000)
testInNewWay(100000)
testInOldWay(1000000)
testInNewWay(1000000)
}
//不進行緩存
func testInOldWay(_ times: Int) {
var string = ""
let date = Date.init()
let startTime = CFAbsoluteTimeGetCurrent();
for _ in 0..<times {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy年MM月dd日"
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 = ""
let date = Date.init()
let startTime = CFAbsoluteTimeGetCurrent();
for _ in 0..<times {
string = DateFormatterCache.shared.dateFormatterOne.string(from: date)
}
let duration = (CFAbsoluteTimeGetCurrent() - startTime) * 1000.0;
print("使用newWay計算\n\(times)次,總耗時\n\(duration) ms\n")
}
}
//建立單例進行緩存
class DateFormatterCache {
//使用方法
//let timeStr = DateFormatterCache.shared.dateFormatterOne.string(from: publishTime)
static let shared = DateFormatterCache.init()
lazy var dateFormatterOne: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "yyyy年MM月dd日"
return formatter
}()
lazy var dateFormatterTwo: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .full
formatter.dateFormat = "EEE, dd MMM yyyy HH:mm:ss z"
formatter.locale = Locale.init(identifier: "en_US")
return formatter
}()
}
複製代碼
使用oldWay計算
1次,總耗時
7.187008857727051 ms
使用newWay計算
1次,總耗時
0.1609325408935547 ms
使用oldWay計算
10次,總耗時
0.552058219909668 ms
使用newWay計算
10次,總耗時
0.05888938903808594 ms
使用oldWay計算
100次,總耗時
4.320979118347168 ms
使用newWay計算
100次,總耗時
0.6080865859985352 ms
使用oldWay計算
1000次,總耗時
47.60599136352539 ms
使用newWay計算
1000次,總耗時
5.526900291442871 ms
使用oldWay計算
10000次,總耗時
427.8249740600586 ms
使用newWay計算
10000次,總耗時
45.81403732299805 ms
使用oldWay計算
100000次,總耗時
4123.620986938477 ms
使用newWay計算
100000次,總耗時
459.98501777648926 ms
使用oldWay計算
1000000次,總耗時
40522.77398109436 ms
使用newWay計算
1000000次,總耗時
4625.54395198822 ms
複製代碼
執行時間統計: git
在測試中,咱們發現執行一次formatter的建立和設置日期格式須要7.187008857727051 ms,執行10次卻只須要0.552058219909668 ms,這是由於第一次執行 let formatter = DateFormatter()
這行代碼時可能會涉及到DateFormatter類相關的一些初始資源的初始化,然後續執行十次時已經不包含這一過程所須要的耗時,因此看上去執行一次的時間反而長一些,咱們在計算性能比較時能夠經過增長執行次數,來忽略這些因素的影響,當咱們執行1000000次時,不進行緩存使用oldWay計算須要40522.77398109436 ms,而一次初始化的開銷最大爲第一次的執行的耗時7.187008857727051 ms,github
7.18/40522.77 = 0.0177%
複製代碼
佔比爲0.0177,這些因素的影響已經下降爲萬分之一了,因此咱們能夠將執行1000000次時,不使用緩存和使用緩存的執行一次所需平均時間方法耗時面試
不使用緩存(oldWay,每次建立DateFormatter對象而且設置格式)
執行一次耗時:40.52 us
使用緩存(oldWay,每次建立DateFormatter對象而且設置格式)
執行一次耗時:4.625 us
使用緩存的方案的執行時間大概是不使用緩存的方案的時間的11.4%
複製代碼
func testPartInOldWay(_ times: Int) {
var string = ""
let date = Date.init()
var startTime1: CFAbsoluteTime = 0;
var startTime2: CFAbsoluteTime = 0;
var startTime3: CFAbsoluteTime = 0;
var startTime4: CFAbsoluteTime = 0;
var duration1: CFAbsoluteTime = 0;
var duration2: CFAbsoluteTime = 0;
var duration3: CFAbsoluteTime = 0;
for i in 0..<times {
startTime1 = CFAbsoluteTimeGetCurrent();
let formatter = DateFormatter()
startTime2 = CFAbsoluteTimeGetCurrent();
formatter.dateFormat = "yyyy年MM月dd日"
startTime3 = CFAbsoluteTimeGetCurrent();
string = formatter.string(from: date)
startTime4 = CFAbsoluteTimeGetCurrent();
duration1 += (startTime2 - startTime1) * 1000.0;
duration2 += (startTime3 - startTime2) * 1000.0;
duration3 += (startTime4 - startTime3) * 1000.0;
}
print("建立DateFormatter對象耗時=\(duration1)ms\n設置日期格式耗時=\(duration2)ms\n生成字符串耗時=\(duration3)ms\n\n")
}
複製代碼
執行1次
建立DateFormatter對象耗時=0.030994415283203125ms
設置日期格式耗時=0.3859996795654297ms
生成字符串耗時=1.6570091247558594ms
執行10次
建立DateFormatter對象耗時=0.019073486328125ms
設置日期格式耗時=0.012159347534179688ms
生成字符串耗時=0.5759000778198242ms
執行100次
建立DateFormatter對象耗時=0.0768899917602539ms
設置日期格式耗時=0.06973743438720703ms
生成字符串耗時=4.322528839111328ms
執行1000次
建立DateFormatter對象耗時=0.7123947143554688ms
設置日期格式耗時=0.702977180480957ms
生成字符串耗時=41.77117347717285ms
執行10000次
建立DateFormatter對象耗時=6.549596786499023ms
設置日期格式耗時=5.913138389587402ms
生成字符串耗時=370.6216812133789ms
執行100000次
建立DateFormatter對象耗時=65.13833999633789ms
設置日期格式耗時=59.78119373321533ms
生成字符串耗時=3586.0002040863037ms
執行1000000次
建立DateFormatter對象耗時=661.7592573165894ms
設置日期格式耗時=575.5696296691895ms
生成字符串耗時=35309.07988548279ms
複製代碼
能夠從輸出結果中發現是string = formatter.string(from: date)
這行代碼耗費時間最多,因此主要耗時並不在於執行DateFormatter.init()和formatter.dateFormat = "yyyy年MM月dd日",在對咱們項目使用Instrument進行分析時,測試結果也證實了這一點緩存
測試環境:iPhone 7性能優化
測試系統:iOS 12.1(16B92)bash
app啓動後的60s內,快速滑動feed流頁面,在這一過程當中,主線程的執行時間大概是10.59s,咱們項目中日期處理主要在func detailString(date: Date) -> String
這個方法中進行,這個方法的運行時間爲730ms,而其中 timeStr = formatter.string(from: date)
這行代碼的運行時間爲628ms,因此也說明了生成日期字符串的方法耗時較多。微信
測試環境:iPhone 7app
測試系統:iOS 12.1(16B92)
測試時間:app啓動後的60s
測試步驟:使用Instruments的Time Profiler啓動app,在啓動後的60s內,快速滑動列表頁。
在咱們項目中,detailString方法每次調用時會建立DateFormatter,生成日期字符串
let formatter = DateFormatter()
formatter.dateFormat = "MM月dd日"
timeStr = formatter.string(from: date)
複製代碼
測試結果:
app啓動後的60s內,主線程執行時間10.59s,detailString的執行730ms
timeStr = DateFormatterCache.shared.dateFormatterOne.string(from: date)
class DateFormatterCache {
//使用方法
//let timeStr = DateFormatterCache.shared.dateFormatterOne.string(from: publishTime)
static let shared = DateFormatterCache.init()
lazy var dateFormatterOne: DateFormatter = {
let formatter = DateFormatter()
formatter.dateFormat = "MM月dd日"
return formatter
}()
複製代碼
咱們經過DateFormatterCache的單例對象shared來獲取dateFormatterOne
測試結果:
從執行時間上對比,緩存後,執行時間是以前的10.4%,對性能的提高仍是比較大的
由於系統內部的實現,咱們看不到源碼,我在私下針對DateFormatter的建立,設置日期格式,生成字符串三個步驟分別作過大量測試,可是也有多是測試方法的侷限性(是經過統計每一個步驟調用時間來彙總的,無法經過調用一百萬次方法來計算總時間來統計的),暫時來講沒法分析出具體是哪一步驟是主要耗時的,可是在項目中,若是使用單例來對建立,設置日期格式這兩個步驟來緩存,使用Instrument進行分析時確實能夠將運行時間降爲不緩存時的10%左右。
Demo在這裏https://github.com/577528249/SwiftDemo/tree/master/DateFormatterDemo
PS:
最近加了一些iOS開發相關的QQ羣和微信羣,可是感受都比較水,裏面對於技術的討論比較少,因此本身建了一個iOS開發進階討論羣,歡迎對技術有熱情的同窗掃碼加入,加入之後你能夠獲得:
1.技術方案的討論,會有在大廠工做的高級開發工程師儘量抽出時間給你們解答問題
2.每週按期會寫一些文章,而且轉發到羣裏,你們一塊兒討論,也鼓勵加入的同窗積極得寫技術文章,提高本身的技術
3.若是有想進大廠的同窗,裏面的高級開發工程師也能夠給你們內推,而且針對性得給出一些面試建議
羣已經滿100人了,想要加羣的小夥伴們能夠掃碼加這個微信,備註:「加羣+暱稱」,拉你進羣,謝謝了