最近看到的Slow App Startup Times裡提到:
The dynamic loader finds and reads the dependent dynamic libraries (dylibs) used by the App. Each library can itself have dependencies. The loading of Apple system frameworks is highly optimized but loading your embedded frameworks can be expensive. To speed up dylib loading Apple suggests you use fewer dylibs or consider merging them A suggested target is for six extra (non-system) frameworks.
至於什麼是static lib什麼是dynamic lib這裡就不展開提了,大家可以通過在文章底部的相關連結去詳細瞭解。
引用一下官方的解釋就是:
- A better approach is for an app to load code into its address space when it’s actually needed, either at launch time or at runtime. The type of library that provides this flexibility is called dynamic library.
- When an app is launched, the app’s code—which includes the code of the static libraries it was linked with—is loaded into the app’s address space.Applications with large executables suffer from slow launch times and large memory footprints
如果給你一個簡單的概念就是如果你的framework使用了swift就是dynamic lib。這也是本文要對比的主題:動態的framework對app啟動時間影響到底多大。
測試的方法就採用 iOS 10 提供的新的環境變數 DYLD_PRINT_STATISTICS
輸出的app啟動時間。Xcode的版本是8.1,測試裝置是iphone 6。cocoapod版本1.1。
注意,測試過程發現每次獲得的時間統計都不一致,所以我這裡的資料可能和你自己測試得到的不同,但是我認為這種偏差不影響定性。
基準線:空的OC專案 VS 空的Swift專案
建立兩個沒有任何業務邏輯的空的專案。
大概有10毫秒的差異。這個差距考慮到測量的偏差可以認為兩者幾乎是一致的。
但是有時會出現swift載入忽然提高到400ms的情況。這是因為系統的動態framework只會載入一份。假設10個app啟動都用到了UIKit,系統內部也只載入了一份UIKit。所以有時swift專案啟動的時候剛好用到了系統framework沒有快取,就會顯得的長一點。
6個framework
現在我們對比有程式碼的情況。兩個專案分別加入5個依賴。
這是OC專案的6個依賴:
1 2 3 4 5 6 |
pod 'AFNetworking', '~> 3.0' pod 'Masonry' pod 'MJRefresh' pod 'SDWebImage' pod 'MBProgressHUD' pod 'IQKeyboardManager' |
這是swift專案的6個依賴,為了模擬真實生產中依然使用一些OC庫的情況,將3個庫換成了swift編碼的,保留了3個OC庫:
1 2 3 4 5 6 |
pod 'Alamofire' pod 'SnapKit' pod 'MJRefresh' pod 'Kingfisher' pod 'MBProgressHUD' pod 'IQKeyboardManager' |
OC的啟動時間在70-100ms左右。這裡取快的情況的資料:
swift專案在第一次安裝時的啟動時間在dylib會多100ms,不知為何。swift專案在已經安裝後執行開啟的成績:
列一個橫向對比圖:
結論
swift專案的dylib時間相比OC多了一百多毫秒,遠遠大於OC。
Swift 不使用framework VS 使用framework
我們猜測是不是載入framework導致時間增大很多呢。所以我們把這些依賴的程式碼全部放在主app裡,不使用framework分離。這次我們把幾個依賴的庫全部換成swift:
1 2 3 4 5 |
pod 'Alamofire' pod 'SnapKit' pod 'Kingfisher' pod 'SwiftDate' pod 'ObjectMapper' |
5個依賴的專案在app已經安裝,執行開啟結果:
考慮到誤差可以認為framework裡的程式碼是OC還是swift對於載入時間的影響並不大。
把依賴的程式碼全都合併到App裡,就是採取手工拷貝的方式:
安裝後開啟的啟動時間統計:
結論
如果把程式碼收到拷貝進app裡,可以顯著降低dylib載入時間。
15個framework OC VS Swift
OC的podfile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
pod 'AFNetworking', '~> 3.0' pod 'Masonry' pod 'MJRefresh' pod 'SDWebImage' pod 'MBProgressHUD' pod 'IQKeyboardManager' pod 'FMDB' pod 'GPUImage' pod 'OpenUDID' pod 'DateTools' pod 'TMCache' pod 'WebViewJavascriptBridge' pod 'ZBarSDK' pod 'JSQMessagesViewController' pod 'YYKit' pod 'DZNEmptyDataSet' |
swift專案的podfile:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
pod 'AFNetworking', '~> 3.0' pod 'Alamofire' pod 'SnapKit' pod 'MJRefresh' pod 'Kingfisher' pod 'MBProgressHUD' pod 'IQKeyboardManager' pod 'FMDB' pod 'GPUImage' pod 'OpenUDID' pod 'SwiftDate' pod 'CryptoSwift' pod 'ObjectMapper' pod 'ZBarSDK' pod 'CocoaLumberjack/Swift' pod 'YYKit' pod 'DZNEmptyDataSet' |
OC的結果:
swift的結果:
對比圖:
縱向對比:
結論
OC的專案隨著依賴增多,初始化時間增大,但是dylib時間增加不明顯。swift則dylib時間大幅增加,初始化時間變化不大。總的啟動時間比OC多了200多毫秒,隨著framework的增多,啟動時間差距拉大。
25個dynamic framework
如果把dylib提高到25個時間又會增長多少呢。
在15個基礎上再新增10個依賴:
1 2 3 4 5 6 7 8 9 10 |
pod 'EZSwiftExtensions' pod 'ReactiveCocoa', '5.0.0-alpha.3' pod 'RxSwift', '~> 3.0' pod 'RxCocoa', '~> 3.0' pod 'MonkeyKing' pod 'Reusable' pod 'SwiftyJSON' pod 'XCGLogger' pod 'Gifu', '~> 2.0.0-rc' pod 'Spring', :git => 'https://github.com/MengTo/Spring.git', :branch => 'swift3' |
執行時間:
和15個dylib的時候的對比:
dylib載入時間從400增加到600,增加了30%左右。dylib數量從15到25個增加40%。接近線性。
總結
毫無疑問,使用了dynamic framework後會增加app啟動時間。如果你的數量在25個左右,相比OC的靜態framework啟動時間會增加0.5s左右。我個人對於iOS提高載入framework的時間不太抱有希望,蘋果讓自定義的framework常駐記憶體似乎也無望,這個時間短期內可能無法抹平。
如果人為的把一些外部依賴手動管理在一個framework也是可行,但是如果複雜一點包的互相依賴的情況會比較費心。
如果公司對效能有著苛刻要求可能500ms是難以忍受的。但是我覺得對於大多數產品而言,犧牲這500ms的效能相比於使用OC,我覺得還是用OC比較難受。
歡迎關注我的微博:@沒故事的卓同學
相關連結:
WWDC 2016 Session 406 Optimizing App Startup Time
What are Frameworks?
iOS 開發中的『庫』(一)
jverkoey/iOS-Framework