The end of fat binaries: XCFrameworks

#blindPeople: The image shows the XCFramework logo with its name written right below.

In 2019, Xcode 11 introduced a highly anticipated feature for every iOS developer: the ability to generate a framework that runs on both the simulator and the device.

Before this, it was already possible using lipo to generate a fat binary. Although feasible, Apple has long discouraged its use and has not offered a viable alternative until now.

Now we can use XCFramework, which generates a pseudo framework containing several frameworks for different platforms.

Let’s assume that your framework supports iOS devices, iOS simulators, and macOS. With XCFramework, we will have the three frameworks built-in, and when the project that uses the framework is built, Xcode will automatically select which of the contained frameworks to use.

Advantages of xcframework

  • Support for multiple platforms in the same framework.
  • Support for Swift, Objective-C, and C.
  • Support for dynamic frameworks and static libraries.
  • Support for Swift Package Manager.
  • End of fat binaries.

Generating an XCFramework

In your project, create a new target (File -> New -> Target) in the “Cross Platform” section with the “Aggregate” type.

#blindPeople: The image shows Xcode’s New Target window with the Aggregate option selected.

In the Build Phases of your target, add a Run Script section and paste the code below:

#blindPeople: The image shows the Build Phases tab of our created Aggregate target, with the code pasted in a Run Script step.
#Gerenare device framework
xcodebuild archive -scheme ${PROJECT_NAME} -archivePath "${PROJECT_DIR}/build/${PROJECT_NAME}-iphoneos.xcarchive" -sdk iphoneos SKIP_INSTALL=NO BUILD_LIBRARIES_FOR_DISTRIBUTION=YES

#Generate simulator framework
xcodebuild archive -scheme ${PROJECT_NAME} -archivePath "${PROJECT_DIR}/build/${PROJECT_NAME}-iossimulator.xcarchive" -sdk iphonesimulator SKIP_INSTALL=NO BUILD_LIBRARIES_FOR_DISTRIBUTION=YES

#Generate xcframework for both arches
xcodebuild -create-xcframework -framework "${PROJECT_DIR}/build/${PROJECT_NAME}-iphoneos.xcarchive/Products/Library/Frameworks/${PROJECT_NAME}.framework" -framework "${PROJECT_DIR}/build/${PROJECT_NAME}-iossimulator.xcarchive/Products/Library/Frameworks/${PROJECT_NAME}.framework" -output "${PROJECT_DIR}/build/${PROJECT_NAME}.xcframework"

#Open directory where xcframework were generate
open "${PROJECT_DIR}/build"

This code archives your project for both the device and the simulator, and then creates the XCFramework. The generated files will be in a folder called “build” in your project directory.

With the archive files, you will obtain the DSYMs of your compilations.

The golden tip

The xcodebuild command “ignores” the BUILD_LIBRARIES_FOR_DISTRIBUTION parameter (probably a bug that will be fixed in later versions). This behavior prevents Xcode from generating the swiftinterface file, which is needed for the create-xcframework command to work.

To solve this problem, the Build for Distribution flag should be set to YES in your project’s build settings as well.

To set this flag, open the Build Settings tab, select All in the exhibition level, and in the Build Options section, change the value of Build Libraries for Distribution to Yes.

#blindPeople: The image shows the Build Settings tab of our Framework, with Build Libraries for Distribution value set to Yes.

Now, run your target again, and everything will work like a charm. You just need to drag the XCFramework file into your main app’s project and Xcode will take care of everything from there.