How to Export "Fat" Cocoa Touch Framework (For Simulator and Device)

How to export fat Cocoa Touch Framework (for Simulator and Device)?

The actuality of this answer is: July 2015. It is most likely that things will change.

TLDR;

Currently Xcode does not have tools for automatic export of universal fat framework so developer must resort to manual usage of lipo tool. Also according to this radar before submission to AppStore developer who is framework's consumer also must use lipo to strip off simulator slices from a framework.

Longer answer follows


I did similar research in the topic (the link at the bottom of the answer).

I had not found any official documentation about distribution of so my research was based on exploration of Apple Developer Forums, Carthage and Realm projects and my own experiments with xcodebuild, lipo, codesign tools.

Here's long quote (with a bit of markup from me) from Apple Developer Forums thread Exporting app with embedded framework:

What is the proper way to export a framework from a framework project?

Currently the only way is exactly what you have done:

  • Build the target for both simulator and iOS device.
  • Navigate to Xcode's DerivedData folder for that project and lipo the two binaries together into one single framework. However, when you build the framework target in Xcode, make sure to adjust the target setting 'Build Active Architecture Only' to 'NO'. This will allow Xcode to build the target for multiple binarty types (arm64, armv7, etc). This would be why it works from Xcode but not as a standalone binary.

  • Also you'll want to make sure the scheme is set to a Release build and build the framework target against release.
    If you are still getting a library not loaded error, check the code slices in the framework.

  • Use lipo -info MyFramworkBinary and examine the result.

lipo -info MyFrameworkBinary

Result is i386 x86_64 armv7 arm64

  • Modern universal frameworks will include 4 slices, but could include more: i386 x86_64 armv7 arm64
    If you don't see at least this 4 it coud be because of the Build Active Architecture setting.

This describes process pretty much the same as @skywinder did it in his answer.

This is how Carthage uses lipo and Realm uses lipo.


IMPORTANT DETAIL

There is radar: Xcode 6.1.1 & 6.2: iOS frameworks containing simulator slices can't be submitted to the App Store and a long discussion around it on Realm#1163 and Carthage#188 which ended in special workaround:

before submission to AppStore iOS framework binaries must be stripped off back from simulator slices

Carthage has special code: CopyFrameworks and corresponding piece of documentation:

This script works around an App Store submission bug triggered by universal binaries.

Realm has special script: strip-frameworks.sh and corresponding piece of documentation:

This step is required to work around an App Store submission bug when archiving universal binaries.

Also there is good article: Stripping Unwanted Architectures From Dynamic Libraries In Xcode.

I myself used Realm's strip-frameworks.sh which worked for me perfectly without any modifications though of course anyone is free to write a one from scratch.


The link to my topic which I recommend to read because it contains another aspect of this question: code signing - Creating iOS/OSX Frameworks: is it necessary to codesign them before distributing to other developers?

How to build a framework in iOS for simulator and device (IPA)

The issue was due to the framework I built was only supporting for Simulator. I resolved the issue by building the framework for both device and simulator. The below post helped me on this.

https://medium.com/swiftindia/build-a-custom-universal-framework-on-ios-swift-549c084de7c8

How to build cocoa touch framework for all architectures dependent on other frameworks added using cocoapods?

So basically I found out that I just need to follow these simple steps.

1. Create a cocoa touch framework.
2. Set bitcode enabled to No.
3. Select your target and choose edit schemes. Select Run and choose Release from Info tab.
4. No other setting required.
5. Now build the framework for any simulator as simulator runs on x86 architecture.
6. Click on Products group in Project Navigator and find the .framework file.
7. Right click on it and click on Show in finder. Copy and paste it in any folder, I personally prefer the name 'simulator'.
8. Now build the framework for Generic iOS Device and follow the steps 6 through
9. Just rename the folder to 'device' instead of 'simulator'.
10. Copy the device .framework file and paste in any other directory. I prefer the immediate super directory of both.

So the directory structure now becomes:

 - Desktop
- device
- MyFramework.framework
- simulator
- MyFramework.framework
- MyFramework.framework

Now open terminal and cd to the Desktop. Now start typing the following command:

lipo -create 'device/MyFramework.framework/MyFramework' 'simulator/MyFramework.framework/MyFramework' -output 'MyFramework.framework/MyFramework'

and that's it. Here we merge the simulator and device version of MyFramework binary present inside MyFramework.framework. We get a universal framework that builds for all the architectures including simulator and device.

Now, creating a pod for this framework doesn't make any difference. It works like a charm. Please also note that there are run scripts available too to achieve the same functionality, but I spent a lot of time in finding the correct script. So I would suggest you use this method.

Can't compile Simulator build with Universal (fat) Framework built with Xcode 10.2+

REASON

The real reason for this issue is in Xcode compiler. Starting Xcode 10.2 Apple changed generator of Framework swift header (MyFramework.framework/Headers/MyFramework-Swift.h). Now it adds lines like

#elif defined(__x86_64__) && __x86_64__
#elif defined(__i386__) && __i386__

to simulator header and

#elif defined(__arm64__) && __arm64__
#elif defined(__ARM_ARCH_7A__) && __ARM_ARCH_7A__

to device header.

So headers for simulator and device become different.
Because general script for universal framework building copy header from device build directory then such framework works fine with device build but fails with Simulator builds.

Apple discover this issue in the Xcode 10.2 release notes Known Issues chapter and suggests a solution.

SOLUTION

Solution to resolve the issue mentioned by Apple is to create combined header which should include both original headers from device and simulator:

#include 
#if TARGET_OS_SIMULATOR

#else

#endif

Regarding mentioned script to make fat Framework you could modify it in next way:

# Step 5. Convenience step to copy the framework to the project's directory
cp -R "${UNIVERSAL_OUTPUT_DIR}/${PRODUCT_NAME}.framework" "${RELEASE_DIR}"

# Step 6. Combine PRODUCT_NAME-Swift.h from device and simulator architectures (Xcode 10.2 issue: 48635615)
UNIVERSAL_SWIFT_HEADER=${UNIVERSAL_OUTPUT_DIR}/${PRODUCT_NAME}.framework/Headers/${PRODUCT_NAME}-Swift.h

> ${UNIVERSAL_SWIFT_HEADER}
echo "#include " >> ${UNIVERSAL_SWIFT_HEADER}
echo "#if TARGET_OS_SIMULATOR" >> ${UNIVERSAL_SWIFT_HEADER}
cat ${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PRODUCT_NAME}.framework/Headers/${PRODUCT_NAME}-Swift.h >> ${UNIVERSAL_SWIFT_HEADER}
echo "#else" >> ${UNIVERSAL_SWIFT_HEADER}
cat ${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PRODUCT_NAME}.framework/Headers/${PRODUCT_NAME}-Swift.h >> ${UNIVERSAL_SWIFT_HEADER}
echo "#endif" >> ${UNIVERSAL_SWIFT_HEADER}

# Step 7. Convenience step to open the project's directory in Finder
open "${RELEASE_DIR}"

Line > ${UNIVERSAL_SWIFT_HEADER} needed to clear copied header in step 2
before combine started.



Related Topics



Leave a reply



Submit