In many of us, we’re iOS developers who are desire to reduce your app’s loading time.
Many and many articles were written to gave you some tricks & tips to improve it, you can check it here Slow App Startup Times,
Someday I go through this WWDC video Optimizing App Startup Time and I try to apply to use MACH-O
with staticlib
instead of dynamic frameworks for all my 3rd-party.
My project is using CocoaPods to manages all of libraries. And luckily it supports us to use staticlib
for any frameworks 👍 http://blog.cocoapods.org/CocoaPods-1.5.0
Pre-main Time - DYLD_PRINT_STATISTICS
To start improve your app, we might to have some metrics to compare before and after the improvements.
Apple has added some logs Logging Dynamic Loader Events to record the timing when user starts launch your app!
We can use one of them: DYLD_PRINT_STATISTICS
Logs statistical information on an application’s launch process, such as how many images were loaded, when the application finishes launching.
Let’s add it into your app’s scheme:
The output afer run the project:
Total pre-main time: 2.0 seconds (100.0%)
dylib loading time: 1.4 seconds (72.8%)
rebase/binding time: 84.22 milliseconds (4.1%)
ObjC setup time: 172.29 milliseconds (8.5%)
initializer time: 291.42 milliseconds (14.4%)
slowest intializers :
libSystem.B.dylib : 14.72 milliseconds (0.7%)
libMainThreadChecker.dylib : 53.90 milliseconds (2.6%)
Braintree : 47.92 milliseconds (2.3%)
FBSDKCoreKit : 46.91 milliseconds (2.3%)
GTMSessionFetcher : 124.46 milliseconds (6.1%)
YourAppName : 46.73 milliseconds (2.3%)
The Total pre-main time
shows how slow app has to take before system executes main()
There are some explaination for the other metrics you can check it here Slow App Startup Times
CocoaPods with staticlib
After watch the WWDC video Optimizing App Startup Time there is a trick that I’m using is to use staticlib
for all libraries from Pods target.
And the result was surprising me, that the pre-main time
was reduced almost 1 second, this is a lot for my project.
Total pre-main time: 993.25 milliseconds (100.0%)
dylib loading time: 580.23 milliseconds (58.4%)
rebase/binding time: 93.87 milliseconds (9.4%)
ObjC setup time: 89.93 milliseconds (9.0%)
initializer time: 229.08 milliseconds (23.0%)
slowest intializers :
libSystem.B.dylib : 12.26 milliseconds (1.2%)
libMainThreadChecker.dylib : 50.70 milliseconds (5.1%)
Braintree : 31.65 milliseconds (3.1%)
Realm : 31.76 milliseconds (3.1%)
YourAppName : 192.47 milliseconds (19.3%)
=> It’s reduced from 2.0 seconds to 993.25 milliseconds, the cold-start time goes down 46.625%
To here you are almost done the improvements, but there is a problem when you do Archive
Found an unexpected Mach-O header code: 0x72613c21
When Archived and you’ll get this message Found an unexpected Mach-O header code: 0x72613c21
when try to Validate App the build from Organizer
I go to check the logs in IDEDistribution.standard.log
:
2019-08-02 09:43:41 +0000 [OPTIONAL] Didn't find archived user entitlements for <DVTFilePath:0x7f9702d2f5d0:'/Users/username/Library/Developer/Xcode/Archives/2019-08-02/YourAppName 8-2-19, 4.43 PM.xcarchive/Products/Applications/YourAppName.app/Frameworks/Cartography.framework'>: Error Domain=NSCocoaErrorDomain Code=4 "Item at "/Users/username/Library/Developer/Xcode/Archives/2019-08-02/YourAppName 8-2-19, 4.43 PM.xcarchive/Products/Applications/YourAppName.app/Frameworks/Cartography.framework" did not contain a "archived-expanded-entitlements.xcent" resource." UserInfo={NSLocalizedDescription=Item at "/Users/username/Library/Developer/Xcode/Archives/2019-08-02/YourAppName 8-2-19, 4.43 PM.xcarchive/Products/Applications/YourAppName.app/Frameworks/Cartography.framework" did not contain a "archived-expanded-entitlements.xcent" resource.}
2019-08-06 04:04:59 +0000 [MT] Canceled distribution assistant
So I guess this due to CocoaPods is treated all those converted staticlib
libraries like dynamic libraries.
[CP] Embed Pods Frameworks
"${PODS_ROOT}/Target Support Files/Pods-YouAppName/Pods-YouAppName-frameworks.sh"
So I digged into this Script Phase
, and find out that after you run pod install
, it will automatically added that script to your app’s Build Phases
.
Let’s check what is inside the script:
install_framework "${BUILT_PRODUCTS_DIR}/Cartography/Cartography.framework"
It will trigger install_framework
for every targets when you do build your project.
So my idea is to prevent this install for all converted libraries from Podfile
.
Integrate with Podfile
I wrote this script improve_pre_main_time_loading.rb, it automatically convert your sepecific libbraries to staticlib
and prevent install_framework
that library from Pods-YouAppName-frameworks.sh
You can download that improve_pre_main_time_loading.rb and add it in post_install
from your Podfile:
post_install do |installer|
# Improve Pre-main Time by using static instead of dynamic libs
require File.expand_path(File.dirname(__FILE__) + "/scripts/improve-pre-main-time-loading.rb")
improve_pre_main_time_loading(installer, "YourAppName")
end
I sepecific which library will use as staticlib
:
supported_staticlib_pods = ['Cartography']
means I only converted Cartography
to staticlib
, you can add more libs here
After this step, you can do pod install
again and do Archive ✅
improve_pre_main_time_loading.rb
Results
- iPhone 6s
- iOS 12.0.1
- There are 65 dependencies from the Podfile and 93 total pods installed.
- Converted 50 dependencies to use
staticlib
Conclusion:
-
Any libs are contained resources are not supported
- Currently, when we do optimize
config.build_settings['SWIFT_OPTIMIZATION_LEVEL'] = '-Owholemodule'
, from time to time Xcode 10 isn’t able to analize Test Coverage when we’re using static-libs https://openradar.appspot.com/41024315 - Convert back to dynamic library: ruby use_dylibs.rb