108

With Xcode 6 we get ability to create own Dynamic Cocoa Frameworks.

enter image description here

Because of:

  • Simulator still use 32-bit library

  • beginning June 1, 2015 app updates submitted to the App Store must include 64-bit support and be built with the iOS 8 SDK (developer.apple.com)

We have to make fat library to run project on devices and simulators. i.e. support both 32 and 64 bit in Frameworks.

But I didn't find any manuals, how to export universal fat Framework for future integration with other projects (and share this library with someone).

Here is my steps to reproduce:

  1. Set ONLY_ACTIVE_ARCH=NO in the Build Settings

    enter image description here

  2. Add support armv7 armv7s arm64 i386 x86_64 to Architectures (for sure)

enter image description here

  1. Build Framework and open it in Finder:

enter image description here enter image description here

  1. Add this framework to another project

Actual result:

But in the end I still have problem with running project with this framework on devices and simulator at once.

  • if I take framework from Debug-iphoneos folder - it works on devices and gets error on simulators: ld: symbol(s) not found for architecture i386

      xcrun lipo -info CoreActionSheetPicker
    

    Architectures in the fat file: CoreActionSheetPicker are: armv7 armv7s arm64

  • if I take framework from Debug-iphonesimulator folder - it works on simulators. and I have error on device: ld: symbol(s) not found for architecture arm64

      xcrun lipo -info CoreActionSheetPicker
    

    Architectures in the fat file: CoreActionSheetPicker are: i386 x86_64

So, how to create a dynamic framework that works on devices and simulators?

This answer related to Xcode 6 iOS Creating a Cocoa Touch Framework - Architectures issues but it's not duplicate.


Update:

I found a "dirty hack" for this case. See my answer below. If someone knows more convenient way - please, let me know!

Community
  • 1
  • 1
skywinder
  • 21,291
  • 15
  • 93
  • 123

6 Answers6

82

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?

Community
  • 1
  • 1
Stanislav Pankevich
  • 11,044
  • 8
  • 69
  • 129
  • 1
    I used lipo but when framework is building in simulator it's showing unresolved identifier with class name but in device it works. If use simulator version of binary then it's working ..any idea? – Susim Samanta Jan 11 '16 at 04:17
  • 2
    I haven't found any evidence this has changed by Xcode 8.2 in December 2016. :/ – Geoffrey Wiseman Dec 18 '16 at 15:28
  • 1
    @Geoffrey, has this changed in Xcode 9.2 or is anything different? This is my first time creating a binary framework for distribution, and I'm already scared... – ScottyB Mar 27 '18 at 14:23
  • Haven't done this in a bit, sadly -- can't say. Good luck. – Geoffrey Wiseman Mar 27 '18 at 15:30
59

This is not so clear solution, but there is only way, that I find:

  1. Set ONLY_ACTIVE_ARCH=NO in the Build Settings

    • Build library for simulator
    • Build library for device
  2. Open in console Products folder for your framework (you can open it by open framework folder and cd .. from there)

enter image description here enter image description here

  1. Run this script from Products folder. It creates fat Framework in this folder. (or do it manually as explained below in 3. 4.)

Or:

  1. Combine these 2 Frameworks using lipo by this script (replace YourFrameworkName to your Framework name)

    lipo -create -output "YourFrameworkName" "Debug-iphonesimulator/YourFrameworkName.framework/YourFrameworkName" "Debug-iphoneos/YourFrameworkName.framework/YourFrameworkName"
    
  2. Replace with new binary one of the existing frameworks:

    cp -R Debug-iphoneos/YourFrameworkName.framework ./YourFrameworkName.framework
    mv YourFrameworkName ./YourFrameworkName.framework/YourFrameworkName
    

  1. Profit: ./YourFrameworkName.framework - is ready-to-use fat binary! You can import it to your project!

For project, that not in Workspaces:

You can also try to use this gist as described here. But it seems, that it not works for projects in workspaces.

Community
  • 1
  • 1
skywinder
  • 21,291
  • 15
  • 93
  • 123
  • I thought Apple doesn't accept fat binaries anymore. https://kodmunki.wordpress.com/2015/03/04/cocoa-touch-frameworks-for-ios8-remix/ – Monstieur Apr 22 '15 at 16:24
  • 1
    @skywinder Did you find any other simple way export Cocoa Touch Framework to ready to use fat binary? I am using the same approach as above but I don't like it. Xcode should have some that automates the process. – dev gr Apr 28 '15 at 10:48
  • 1
    @devgr not yet.. that's why I didn't accept my own answer. Still looking for a better solution. – skywinder May 04 '15 at 06:44
  • @skywinder, I also use similar script with lipo in it that combines: iphoneos + iphonesimulator => far binary, see [my question and answer](http://stackoverflow.com/questions/30963294/creating-ios-osx-frameworks-is-it-necessary-to-codesign-them-before-distributin). Also: [Realm does it](https://github.com/realm/realm-cocoa/blob/d59c86f11525f346c8e8db277fdbf2d9ff990d98/build.sh#L108), [Carthage does it](https://raw.githubusercontent.com/Carthage/Carthage/master/Source/CarthageKit/Xcode.swift) so looks like that is what should be done unless Apple automates this process better. – Stanislav Pankevich Jun 30 '15 at 17:52
  • @Stanislaw yep, thanks for the link. And and you also start a bounty recently. nice coincidence. – skywinder Jul 01 '15 at 07:36
  • Yes, I also noticed that coincidence :) – Stanislav Pankevich Jul 01 '15 at 12:58
  • @Stanislaw please, make answer from your comment about realm and Carthage with links and some istructions. And I will give you this bounty. It's most closest thing for the answer. (it expires in 2 hours.. don't want just throw away 100 points ;) – skywinder Jul 07 '15 at 08:10
  • Thank you so much for this! – 72A12F4E Dec 11 '15 at 22:40
  • 1
    Cannot run in the simulator but works in device with the 3 and 4 steps – jose920405 May 05 '16 at 21:22
  • 1
    @could anyone explain why only `Debug-` folder is used with `lipo -create`? Could this framework be used for `Release` configuration and why? Thanks. – Yevhen Dubinin Jun 02 '16 at 13:04
  • This is a shame XCode doesn't provide a clean and homogeneous way to fat a binary to specific architectures. This is the worst IDE I have ever seen. Where, as you can see, it is only a matter of scripting. – Mr Bonjour Jun 14 '16 at 09:21
  • If anyone else is having problems running it on the simulator, jose920405 figured out how to [solve](http://stackoverflow.com/a/38001371/2362508) it – Leonardo Oct 05 '16 at 19:34
  • Since the framework is usually given to other people/projects to include, it would be better to make release build here, then use the same command given by skywinder, except to create it from the Release-iphoneos and release-iphonesimulators folders. – CodeBrew Nov 15 '17 at 02:06
  • I had to go to the link to get the updated version of the script. WORKS! – zumzum Sep 02 '18 at 12:14
11

@Stainlav answer was very helpful but what I did instead was to compile two versions of the framework (one for the device and one for simulator) and then added the following Run Script Phase to automatically copy the precompiled framework required for the running architecture

echo "Copying frameworks for architecture: $CURRENT_ARCH"
if [ "${CURRENT_ARCH}" = "x86_64" ] || [ "${CURRENT_ARCH}" = "i386" ]; then
  cp -af "${SRCROOT}/Frameworks/Simulator/." "${SRCROOT}/Frameworks/Active"
else
  cp -af "${SRCROOT}/Frameworks/Device/." "${SRCROOT}/Frameworks/Active"
fi

This way I don't have use lipo to create a fat framework neither the Realm's strip-frameworks.sh to remove the unnecessary slices when submitting to the App Store.

odm
  • 888
  • 1
  • 14
  • 22
  • Which one do you link against? – Jaka Jančar May 24 '16 at 23:43
  • @JakaJančar I link against the ones in the `${SRCROOT}/Frameworks/Active` folder. They get replaced for the right precompiled frameworks for the active architecture on compile time. – odm Jun 08 '16 at 20:45
  • 2
    Love it! This is so much simpler than the combine-then-tear-apart `lipo` approach. – clozach Oct 05 '17 at 20:49
2

basically for this i found very good solution. you 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'.
  9. 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.

2

My Answer cover the below points:

  • Make framework which works for both simulator and Device

  • How to export “fat” Cocoa Touch Framework (for Simulator and Device both)?

  • Undefined symbols for architecture x86_64

  • ld: symbol(s) not found for architecture x86_64

Steps 1: First build your frameworks with Simulator target

Steps 2: After success of simulator building process, now build for your framework with device target selection or Generic iOS Device selection

Step 3: Now select your framework target and for that Under "Build Phases" select "Add Run Script" and copy the below script code)

Step4: Now finally, build again and your framework is ready for both simulator and device compatibility. Hurray!!!!

[Note: We must have both compatible framework ready before final step4 (simulator and device architecture compatible, if not please follow above steps 1 and 2 correctly)

See the reference image:

enter image description here

enter image description here

Put below code into shell area:

#!/bin/sh


UNIVERSAL_OUTPUTFOLDER=${BUILD_DIR}/${CONFIGURATION}-universal


# make sure the output directory exists

mkdir -p "${UNIVERSAL_OUTPUTFOLDER}"


# Step 1. Build Device and Simulator versions

xcodebuild -target "${PROJECT_NAME}" ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphoneos  BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build

xcodebuild -target "${PROJECT_NAME}" -configuration ${CONFIGURATION} -sdk iphonesimulator ONLY_ACTIVE_ARCH=NO BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" clean build


# Step 2. Copy the framework structure (from iphoneos build) to the universal folder

cp -R "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework" "${UNIVERSAL_OUTPUTFOLDER}/"


# Step 3. Copy Swift modules from iphonesimulator build (if it exists) to the copied framework directory

SIMULATOR_SWIFT_MODULES_DIR="${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule/."

if [ -d "${SIMULATOR_SWIFT_MODULES_DIR}" ]; then

cp -R "${SIMULATOR_SWIFT_MODULES_DIR}" "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/Modules/${PROJECT_NAME}.swiftmodule"

fi


# Step 4. Create universal binary file using lipo and place the combined executable in the copied framework directory

lipo -create -output "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphonesimulator/${PROJECT_NAME}.framework/${PROJECT_NAME}" "${BUILD_DIR}/${CONFIGURATION}-iphoneos/${PROJECT_NAME}.framework/${PROJECT_NAME}"


# Step 5. Convenience step to copy the framework to the project's directory

cp -R "${UNIVERSAL_OUTPUTFOLDER}/${PROJECT_NAME}.framework" "${PROJECT_DIR}"


# Step 6. Convenience step to open the project's directory in Finder

open "${BUILD_DIR}/${CONFIGURATION}-universal"
Sandip Patel - SM
  • 3,346
  • 29
  • 27
  • This script seems to call itself and thus causing an infinite loop!! Had to restart my computer after running it! Was continually spawning new xcodebuild processes... and opening new finder windows - Would down vote – J.beenie May 02 '17 at 21:58
  • Check out the answer of @l0gg3r in this [SO Q/A](http://stackoverflow.com/questions/28652650/how-to-build-cocoa-touch-framework-for-i386-and-x86-64-architecture) for the a similar script without the recursion problem. – J.beenie May 03 '17 at 23:02
2

I just want to update this great answer by @odm. Since Xcode 10, the CURRENT_ARCH variable doesn't reflect the build architecture anymore. So I changed the script to check the platform instead:

echo "Copying frameworks for platform: $PLATFORM_NAME"
rm -R "${SRCROOT}/Frameworks/Active"
if [ "${PLATFORM_NAME}" = "iphonesimulator" ]; then
    cp -af "${SRCROOT}/Frameworks/Simulator/." "${SRCROOT}/Frameworks/Active"
else
    cp -af "${SRCROOT}/Frameworks/Device/." "${SRCROOT}/Frameworks/Active"
fi

I also added a line to clear the target directory before copying, because I noticed that additional files in subdirectories wouldn't be overwritten otherwise.

Dorian Roy
  • 3,094
  • 2
  • 31
  • 51