最近看到的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項目
創建兩個沒有任何業務邏輯的空的項目。
純OC項目的啟動時間
純swift空項目的啟動時間
大概有10毫秒的差異。這個差距考慮到測量的偏差可以認為兩者幾乎是一致的。
但是有時會出現swift加載忽然提高到400ms的情況。這是因為系統的動態framework只會加載一份。假設10個app啟動都用到了UIKit,系統內部也只加載了一份UIKit。所以有時swift項目啟動的時候剛好用到了系統framework沒有緩存,就會顯得的長一點。
6個framework
現在我們對比有代碼的情況。兩個項目分別加入5個依賴。
這是OC項目的6個依賴:
pod 'AFNetworking', '~> 3.0' pod 'Masonry' pod 'MJRefresh' pod 'SDWebImage' pod 'MBProgressHUD' pod 'IQKeyboardManager'
這是swift項目的6個依賴,為了模擬真實生產中依然使用一些OC庫的情況,將3個庫換成了swift編碼的,保留了3個OC庫:
pod 'Alamofire' pod 'SnapKit' pod 'MJRefresh' pod 'Kingfisher' pod 'MBProgressHUD' pod 'IQKeyboardManager'
OC的啟動時間在70-100ms左右。這裡取快的情況的數據:
OC6個依賴的啟動時間
swift項目在第一次安裝時的啟動時間在dylib會多100ms,不知為何。swift項目在已經安裝後運行打開的成績:
swift6個依賴
列一個橫向對比圖:
結論
swift項目的dylib時間相比OC多了一百多毫秒,遠遠大於OC。
Swift 不使用framework VS 使用framework
我們猜測是不是加載framework導致時間增大很多呢。所以我們把這些依賴的代碼全部放在主app裡,不使用framework分離。這次我們把幾個依賴的庫全部換成swift:
pod 'Alamofire' pod 'SnapKit' pod 'Kingfisher' pod 'SwiftDate' pod 'ObjectMapper'
5個依賴的項目在app已經安裝,運行打開結果:
5個依賴
考慮到誤差可以認為framework裡的代碼是OC還是swift對於加載時間的影響並不大。
把依賴的代碼全都合並到App裡,就是采取手工拷貝的方式:
安裝後打開的啟動時間統計:
對比圖:
結論
如果把代碼收到拷貝進app裡,可以顯著降低dylib加載時間。
15個framework OC VS Swift
OC的podfile:
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:
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個依賴:
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