RevenueCat / purchases-ios

In-app purchases and subscriptions made easy. Support for iOS, watchOS, tvOS, macOS, and visionOS.
https://www.revenuecat.com/
MIT License
2.2k stars 294 forks source link

[Proposal] XCFramework binary size #3988

Open omarzl opened 1 week ago

omarzl commented 1 week ago

Hello, before sending any pull requests, I opened this issue to propose some improvements that could benefit the binary size of the pre-compiled xcframework.

Current size

First, I calculated the current size of the SDK by uploading the MagicWeatherSwiftUI app with and without the SDK to TestFlight. The values correspond to an iPhone 6S.

These are the results:

Download Install
Including it 1.26 MB 3.56 MB
Excluding it 110 KB 357 KB

Therefore, RevenueCat SDK has a size impact of: 1.15 MB in Download size and 3.2 MB in Install size

Stripping symbols

I found out that the binary contains local symbols used for debugging purposes, so they can be stripped from the binary since they aren't necessary in production.

By running:

xcrun strip -x RevenueCat.framework/RevenueCat -o RevenueCat.framework/RevenueCat

I had these results:

Download Install
Base 1.26 MB 3.56 MB
Symbol stripping 1.21 MB 3.3 MB
Difference -0.5 MB (-4%) -0.26 MB (-7%)

Compilation optimization

Currently, the frameworks have been compiled using -O optimization, also called speed optimization.

But the SDK code doesn't look cpu intensive, so I suggest changing it to -Osize to further reduce the binary size.

By changing it and also stripping the symbols, these are the results:

Download Install
Base 1.26 MB 3.56 MB
-Osize & symbol stripping 1.18 MB 3.04 MB
Difference -0.8 MB (-6%) -0.52 MB (-14%)

Linking

Using static linking benefits the binary size since the linker can know which symbols are actually used and only copy them to the app's binary. Also, it can perform additional optimizations as machine outlining, while the dynamic linker (dyld) can't, because it links the framework at runtime.

It also benefits the app launch time; when you have a large number of dynamic frameworks, it slows down.

By changing the optimization to Osize and linking the framework statically, these are the results:

Download Install
Base 1.26 MB 3.56 MB
Osize & static linking 1.12 MB 2.83 MB
Difference -0.8 MB (-11%) -0.52 MB (-20%)

Note: we can't strip symbols in this scenario because the app is the one who will link the binary.

Note 2: There is a scenario where static linking can increase the binary size instead of reducing it, and it is when the SDK is being used by the app and an app extension. In this case, the symbols will be duplicated in both binaries, so in this case, it is preferred to link dynamically.

Mergeable framework

A mergeable framework takes the best of both worlds! In debug configuration, the linker reexports the code so it can be linked dynamically. This benefits the developer by not linking it in each incremental build.

While in the Release config, the linker merges the code into the app's binary, similar to static linking. This benefits the user, as said before, by having a smaller binary and a faster app launch.

I did some tests, and I was able to compile and create a mergeable library, but at runtime it fails since the load commands are being duplicated for some reason. This will take more time to make it work.

Recommendations

I recommend providing your clients with the 3 options:

1.- The current dynamic framework xcframework Just adding a code strip script and changing the compilation optimization to -Osize optimization

2.- A static framework xcframework Compiling it with -Osize optimization

3.- A mergeable framework xcframework This kind of fwks are not extensively being used by many companies yet, but why not being one of the first ones 😄

RCGitBot commented 1 week ago

👀 We've just linked this issue to our internal tracker and notified the team. Thank you for reporting, we're checking this out!