Minimising an embedded swift framework size - ios

I'm trying to figure out how to make my embedded swift framework as small as possible in a host app that is using it.
My framework is compiling to a 12MB universal .framework file (release mode) that can be used in both simulator and device builds. After I embed it to an app, the app itself is gaining about ±3.4MB of size (post-compile), 3.4MB is pretty big and I think it can be minimised.
I tried to play with most of the compilation options.
In build settings I disabled the generating of debug symbols and I also checked that in release they are stripped out.
strip symbols &
generate debug symbols
How can I make sure that when my swift framework is embedded to an app it will add as ״little״ as possible to the app overall size ?
Thanks!

Try running lipo -info on the binary file inside the framework. If it specifies unnecessary architectures you can remove those, also with lipo:
lipo MyFramework.framework/MyFramework -remove i386 -output MyFramework

Related

My swift static framework is doesn't work in OC file with iphone simulator

I have made a static framework that mix of Swift and OC, and it includes Architectures: "armv7 i386 x86_64 arm64".
it also includes all .swiftmodule file:
it works well with real device or in swift file for real device and simulator.
but only when i use framework's swift class in the Objective-C file and target device is iphone simulator, it will give me an error:
the BaseNavigationController(write in swift), BaseViewController(write in OC) are my framework classes, only the swift class doesn't work, the BaseNavigationController is a subclass of UINavigationController with 'open' level access control, and the framework '-Swift.h' file has generated oc interface.
Why does it report an error?I checked my project carefully,but no exception found。
my xcode version is: Version 11.5 (11E608c), and project build_setting's swift version is 5.0.
The issue here is very likely in the build script that is used.
The issue here might be that after using lipo to merge the framework built for devices(armv7 & arm64 architectures) and simulators(i386, x86_64) architectures, you should also copy contents of the BaseCore-Swift.h generated for the simulator to the one generated for armv7 & arm64 architectures.
Objective-C requires symbols to be defined for i386 and x86_64 architectures in the BaseCore-Swift.h file for Swift classes to work. This file can be found within the Headers folder after you perform a build.
You could simply use the cat command to perform this copy within your build script if you have one.
The issue here is not that architectures for the simulator are not included, but that the symbols are not defined in the BaseCore-Swift.h file for simulator architectures so Objective-C can pick them up.
Please feel free to comment or ask additional queries.
If doing the above solves your issue, please mark this as accepted :)

'MyClass' is unavailable: cannot find Swift declaration for this class - Simulator

I have my framework and I am supporting different architectures. For supporting simulator I am using x86_64 in valid architectures.
But when I use my framework in the app, I get the error while running the app on simulator :
'MyClass(In framework)' is unavailable: cannot find Swift declaration
for this class
Note : It works fine on device.
If memory serves me right, since 6-th version Xcode doesn't support so-called "universal" frameworks (frameworks that contains architectures for arm-family and x86_64/i386). So now when you build a framework Xcode will make two separate bundles for iphoneos and iphonesimulator (you can find them under corresponding folders in your derived data folder). The architectures these frameworks will contain depends on ARCHS variable. By default it's set to $ARCHS_STANDARD, that varies between platforms. You can play around with this setting, mixing architectures you really need, but Xcode will fail at build time if these architectures are incompatible (presumably x86_64 and arm-family architectures are considered incompatible).
To get round this limitation you actually have to do a little bit of "hard-work" yourself and "merge" framework bundles for simulators and devices manually.
1. Build your framework for iOS device and simulator
Here is nothing fancy, just go to your target build settings and ensure that "Build Active Architecture Only" (ONLY_ACTIVE_ARCH) is set to NO and add all required architectures for "Valid Architectures"(VALID_ARCHS, you have already done that):
Now build your framework, find the bundle file under the Product group in Xcode, and open it in Finder:
You should find two folders, one for each set of architectures:
2. Merge two frameworks into one
Now go into Debug-iphoneos folder, copy framework from there and paste it somewhere else, for example in the parent folder:
It will contain our universal framework in a short while. In order to do that we need first create universal dynamic library with the lipo tool. Open terminal, navigate to the folder where you are now (with the copied framework file) and type as follow:
$ lipo -create -output 'MyFramework' 'Debug-iphoneos/MyFramework.framework/MyFramework' 'Debug-iphonesimulators/MyFramework.framework/MyFramework'
If you are not in the derived data folder your paths for the framework libraries will differ of course. This command will produce new universal library containing both sets of architectures. Now just drag and drop it into your framework bundle and replace the existing one.
It's likely that your framework has architecture slices under a folder like MyFramework.framework/Modules/MyFramework.swiftmodule. Our universal framework should have slices for all supported architectures. You should already have arm slices inside, since we copied our framework from the Debug-iphoneos folder, so just find the same folder for the Debug-iphonesimulator and copy files from the folder to the destination framework's folder. You now should have this folder look something like that:
That's it! Now you have a universal framework that should work properly on both an iOS simulator and an iOS device.
3. Slice your framework when submitting to the Appstore
If you try to submit an application alongside a universal framework, it will be automatically rejected. So before submitting you will need to extract only devices' architectures one by one and then merge them into one framework using lipo:
$lipo -extract 'arm' 'MyFramework.framework/MyFramework' -output 'MyFramework-arm'
$lipo -extract 'arm64' 'MyFramework.framework/MyFramework' -output 'MyFramework-arm64'
$lipo -create -output 'MyFramework' 'MyFramework-arm' 'MyFramework-arm64'
Then copy it into your framework and remove redundant slices.
P.S.
Hopefully it helps, however this is kind of well known issue and there is already a lot of solutions in the internet. I suggest you googling terms like 'Create fat/universal framework iOS', and I believe you will find a lot of working scripts that will do all this stuff for you. Here is one I find the most helpful.
I see two possible options:
Your device and your simulator use different iOS versions, and your framework is using something from the latest iOS versions (also, I see that you have the option "Build Active Architecture only" switched to 'No'. Try to switch it back to 'Yes' and make a clean build. Probably, the compiler will find an issue, but it depends on how you link your framework to the app).;
Your framework built for a specific device. Try to switch to the 'framework' target, switch destination for build to 'Generic iOS device' and make clean builds for the 'framework' and the app.
Also, it will help if you update the question with the brief description of how you link your framework (via Workspace / via binary / via CocoaPods / etc.).
Usually, such issues caused by some stupid things, and you should check all theories, even dump or misleading at first sight :)

What is the difference between Build Active Architecture Only -> No and creating a fat binary by using lipo?

Let's imagine that we're creating a static library in Xcode and in the Build Settings we set Build Active Architecture Only to No. Why does it still produce a library suitable only for the device currently selected (a simulator or real device)? What do we still need to create a fat binary running a script which would use lipo tool? Why at is the actual difference between these two? What does lipo do that building for all architectures doesn't?
When you are Building you are using only one architecture to Debug on, to do a fat library your action of choice must be Archive that also strip all debug info from the final library.
As for why after setting NO it still builds only for currently selected device, check if the Configuration your are editing is Debug or Distribution/Release.

iOS universal framework with iphoneos and iphonesimulator architectures

xcodebuild can build a project with sdk set to either iphoneos or iphonesimulator but not both, so in order to generate a framework containing armv7 arm64 and i386 x86_64 architectures, I have to run xcodebuild twice and then use lipo to combine all the architectures into 1 universal binary. I see commercial framework that does this but it result in an incorrect info.plist file because it has a field, CFBundleSupportedPlatforms and all signs point to it only containing 1 value, e.g., CFBundleSupportedPlatforms = ( "iPhoneSimulator" ).
Seems like lipo shouldn't be used in this manner because it isn't officially supported by xcodebuld. Is there a better way to build a framework to contain all architectures?
I follow the question, but I suppose I am a bit puzzled why you want to unnecessarily bloat a single .framework with simulator-only i386 and x84_64 slices that are really only relevant to your development builds. Would you by chance be wanting to distribute a framework to other developers and want to make it work on simulator as well as device?
If so, you are on the right track with using lipo to join the thin binaries for device together or to join the thin binaries for simulator together, but shouldn't be trying to spawn one single device and simulator framework. Apple's own use of SDKs and Frameworks serves as the guide here. Within Xcode, there are two different platform SDKs -- iPhoneOS.platform and iPhoneSimulator.platform that contain the SDKs with only the slices for the relevant target architectures:
You can drill into each of these folders and find the UIKit frameworks do indeed follow the per-platform idea and are conditionally linked based on the SDK that is in use:
I'd further guess that you wanted to have one universal, all-architectures framework so that consuming developers didn't have to remember to swap out one .framework file for another depending on how they were compiling the app. The great news is that you can use conditional linking flags to be able to affect this without needing to do file-system swaps!
As folks adopt your library, part of the setup should be to use conditional linking -- Within the OTHER_LINKER_FLAGS option you can have per-configuration (Debug, Release, Ad-Hoc, etc.) build settings and can also have per-Architecture or per-SDK specific settings too:
To get access to these SDK-specific settings you'll need to click the + next to each of your build configurations where you want to custom tailor framework linking. You can then select the appropriate SDKs from the dropdown list and add your linker flags for each of the two target frameworks.

Are fat libraries stripped when building for release in Xcode

I have a fat (armv7 + i386) library I use for development now. Works fine but now I am concerned if Xcode will strip other architectures (i386) and other configurations (Debug for example) when building for release?
I don't plan to debug this library since it is very old and stable. I just want to use it so I guess I don't need all debug symbols (?), furthermore when building for release I won't even need the i386 since that is for the simulator.
Do I need to set up something in Xcode so it gets stripped or I should better be using a non-fat library? If so how can I control this? :)
This library is about 500KB so I prefer not to put more info than I need.
The library stores separate object files for different architectures (i386, armv7, armv7s etc.). When you build the final app, it will only retrieve the required architectures and only the required object files from the library. The final version for the App Store will certainly not contain any i386.
Regarding release and debug configuration, it's different. This concept is not known by the library tool. So it cannot store separate debug and release versions. And when building the app, it will take whatever it finds. So to minimize your final app, you'll need to build both the library and the app with release settings.

Resources