iOS universal framework with iphoneos and iphonesimulator architectures - ios

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.

Related

'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 :)

How to build dynamic Framework for iOS with no Embedded Binaries?

I try to generate framework named AfTestFramework for ios. I created through Xcode Cocoa Touch Framework with some basic code, ran it and got:
So far so good.
I created new Test Application and drag # dropped AfTestFramework.framework into the project, like crashlytics does (see below)
After building project and running I got Library not loaded ... Reason: image not found:
dyld: Library not loaded:
#rpath/AfTestFramework.framework/AfTestFramework Referenced from:
/var/containers/Bundle/Application/9B12D0AD-94AA-4119-A5DE-6BCAA806FA9F/TestAvoidEmbedded1.app/TestAvoidEmbedded1 Reason: image not found
I know the solution is to attach this framework to Embedded Binaries but I look for the way to avoid this approach.
Further, It demands to create 2 Frameworks: for Simulator and for Release otherwise I cannot release application with my framework, that looks a bit messy and weird.
I found that following frameworks work without Embedded Binaries:
crashlytics
firebase
facebook
Chartboost
ans so on.
So far I didn't find any description, I know also that its something new.
I started to play with xcodebuild like:
xcodebuild -sdk "iphoneos10.1" "ARCHS=arm64 armv7 armv7s" "VALID_ARCHS=arm64 armv7 armv7s" "ONLY_ACTIVE_ARCH=NO" -target AfTestFramework -configuration "Release" clean build
but no success.
Can somebody spread the light on this problem?
If you add a dynamic Framework to your iOS project, you must add it to the Embedded Binaries. In opposite to static libraries are dynamic libraries loaded at runtime. Thus the dynamic linker must access them on the device.
The only possible way to add the code inside of the framework without embedding the framework is to bind it statically to your app. For that you should create a static library from the framework. (Probably, libtool(1) may help you with that.) But anyway you may run into other problems, if the framework needs its bundle structure for loading resources etc.
I'm not quite sure what you are trying to achieve but it sounds like you are trying to create a framework which has a dependency on another framework.
So when used, it would be something like app -> your-framework -> some-other-framework.
I would strongly recommend looking at carthage as a solution to our problem. Carthage is an OS X native dependency manager that is designed to manage dependencies for you.
Firstly you need to check that the framework your framework uses is Carthage friendly. For now I'll assume so.
In your frameworks project you would add a Cartfile where you declare your dependency on the other framework, then use Carthage to download and build the other framework. You then add the built framework to your project as you would with any Apple supplied framework. You DO NOT need to embed the framework and generally speaking it's not recommended to do so. Carthage will handle this issue.
In the app project you would then also add a Cartfile, but you only need to specify your project in it. When Carthage builds the framework, it will automatically locate the other framework, download and build it as well. Then in the apps build phases you simple specify linking agains both frameworks and add a carthage-copy script phase to include them in the Frameworks directory of the finished app.
With regard to building for simulator and release. You don't need to build multiple targets. For a start, using Carthage means that the frameworks are only built when you need them, so this removes any requirement for you to supply binaries.
When telling Carthage to build the dependencies, by default it builds Release binaries. Building Debug frameworks for testing is just a matter of add --configuration Debug to Carthage's command line arguments.
Finally - to be fair, there are also two other package managers out there: Ruby based CocoaPods which is an older manager and for Swift, the Swift Package Manager. Personally I prefer Carthage for reasons I explain in a blog post here.
this is old but if its useful for future reference:
you can do
$lipo -create <path to simulator framework> <path to device framework> -output <path to the output framework>

How to build a Framework fat binary (all archs) that still compatible with archive and upload to AppStore?

I've built a Framework for distribution (not open source),everything is working despite Xcode not having a nice support for developing Frameworks.
The problem is that when archiving for AppStore you get the error:
The executable <EXECUTABLE_NAME_AND_PATH>.framework contains unsupported architectures[x86_64,i386]
The solution to the error above is to strip out the architectures mentioned, as already discussed in this question and in other sources as well.
The problem is that to use the binary on Xcode it is necessary to have all architectures, but to archive I cannot have all of them. How to build the framework in a way that it contains all the architectures (or in a way that simulators accept it) and still be able to archive and upload to AppStore without using the custom scripts to strip the exceeding architectures?
It looks like you need to build two fat libraries, one for development and another one for deployment.
This way you can create two targets into your project (you can duplicate the existing target). One target will be used for development and be linked with the full framework. The other target will be used for archiving and will be linked to the reduced framework.
Hoping this helps...

Minimising an embedded swift framework size

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

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