Recently, mobile developers have struggled when integrating binary frameworks in their iOS apps. Does this Xcode error look familiar?

Building for iOS Simulator, but the linked and embedded framework 'MyFramework.framework' was built for iOS + iOS Simulator.

The reason for this error stems from Apple’s introduction of M1 chips in the latest Macs. This policy decision introduced a side effect of breaking functionality in binary distribution in Xcode 12. Previously, in Xcode 11, you would get compiler warnings, but your build would still work. That is no longer the case, and the writing is on the wall for the old way of distributing binaries.

In this post, we’ll cover the following topics:

  • How Apple’s new hardware created this problem
  • How XCFramework is Apple’s solution
  • How this fits into Apple’s grand plan
  • Troubleshooting steps for Xcode 12 and XCFrameworks

How Apple’s new hardware created this problem

In November 2020, Apple started releasing Macs with a new class of hardware called the M1, which is a CPU that uses the arm64 architecture. Traditionally, Macs had CPUs that used x86-64. Newer iPhones also use the arm64 architecture, and this is not a coincidence. This consolidation into one CPU architecture brings many amazing benefits for Apple and their users.

The biggest benefit is the ability to have cross-platform codebases.

  • For Apple, they can build and test one file system module, one UIKit module, etc., and they will run on both iPhone and Macs. This allows Apple’s teams to share code and test coverage.
  • For developers, their iOS apps can now run on Macs with very few changes. They can address an entire new market without doing any work.
  • For users, they are able to use any iPhone app on their Mac.

Additionally, there are significant performance and power efficiency improvements in M1 compared to any existing architectures.

This all sounds great, so what is the problem with having Macs and iPhones share the same CPU architecture?

The problem stems from the previous way of linking binary frameworks. These frameworks were shipped as “fat” frameworks, meaning a vendor would build a single framework that contained all the required architectures. Historically, that would mean a single binary file had slices like the following:

  • arm7 or arm64 for iPhone
  • x86-64 for Macs

The linker would pick one of the architectures to use at build time depending on the hardware destination. Since the three languages are all separate, it was easy to have different rules for simulator versus device. In other words, since the architectures for iPhones did not overlap with the architecture for Macs, the linker would never get tripped up over which binary to use for a given target.

Now, however, the corresponding binary file would have slices like the following:

  • arm64 for iPhone
  • arm64 for Mac

They have the same architecture name, but the code for each target is very different. The iOS Simulator has different rules than the iPhone, and you cannot run a binary built for one on the other. The previous way to link binary frameworks, lipo, is unable to deal with a merged binary containing multiple binaries that share the same architecture.

That’s why this error shows up:

Building for iOS Simulator, but the linked and embedded framework 'MyFramework.framework' was built for iOS + iOS Simulator.

The binary framework contains different code for the same architecture in multiple places, and Xcode doesn’t know how to handle it.

How XCFramework is Apple’s solution

XCFramework is Apple’s answer to the puzzle described above. In XCFramework, you no longer build a single framework with multiple architectures. Instead, you build one small framework for each combination of architecture and target and store it in its own folder. Your top-level XCFramework folder would then have folders like ios-arm64, ios-arm64-simulator, etc. Each of these folders is its own framework, complete with headers, modules, and binary.

This division into individual frameworks means you no longer have to spend time merging and then stripping platform binaries when linking. For vendors, it also means you can distribute a single XCFramework containing all your target platforms. With lipo, you were forced to limit your framework to only contain unique architectures regardless of the number of platforms you wanted to support.

How this fits into Apple’s grand plan

Apple receives, hosts, processes, and sends all the code that users ultimately download from the App Store. And that is a lot of code. Anything they can do to reduce this number equates to less time and money spent on their end and less bandwidth required on downloads for users.

Many improvements here are strictly win-win. Apple’s introduction of Bitcode is a great example:

  • Apple can compile your app to be optimized for target devices and operating system versions.
  • Apple can recompile your app later to take advantage of specific hardware, software, or compiler changes.
  • Apple can reoptimize your app without the need to submit a new version to the App Store.
  • Apple can reduce the download size for users by removing unneeded code.

Ultimately, the shift to using XCFrameworks is an improvement along the same lines as Bitcode. Apple’s goal is to reduce the amount of code that developers send their way, which allows them in turn to be ever more efficient.

One has to wonder if this is at least in part a jab from Apple against binary frameworks, given the lack of information and transparency provided to help developers seamlessly transition from lipo frameworks to the new XCFrameworks. After all, open-source frameworks that compile to Bitcode have not had any wrenches thrown their way with Xcode 12. Perhaps Apple hopes vendors suffer enough headaches with clients during this transition to potentially reconsider how they provide their software. Apple may view this as a step in the direction they ultimately want us to go…

Enough speculation. Let’s now go over some troubleshooting steps if you are a vendor or developer having difficulty with recent versions of Xcode.

Troubleshooting steps for Xcode 12 and XCFrameworks

We’ll go over the four main ways of implementing dependencies and how to troubleshoot issues with binary frameworks and Xcode 12.

Manual

When using Xcode 12 or newer, you should use XCFrameworks. For Xcode 11 or older, you need to use lipo fat frameworks. Starting in Xcode 12.3, you cannot use lipo frameworks. Soon, Apple will require all developers to use Xcode 12 for code signing, at which point vendors that have not updated their binaries to XCFrameworks will not be able to be released in new app versions.

Swift Package Manager

Swift Package Manager only started fully supporting binary frameworks of any kind in Xcode 12.2. Thus, developers who use vendor binaries must be on at least Xcode 12.2. There are no SPM options for older Xcode versions due to bugs in Apple’s binary implementation.

CocoaPods

CocoaPods implemented XCFramework support earlier than most dependency managers. However, it is still recommended to upgrade to 1.10.1, or the latest pod utility.  You can check the pod version by typing pod --version into the command line. Anything lower than 1.9.3 will not work.

Additionally, you also need Xcode 12.2 or newer for XCFramework support. So a developer using CocoaPods needs at least Xcode 12.2 AND pod 1.10.1.

Note: you might need to deintegrate an XCFramework in order to get things working. That process works like this:

  1. Remove the XCFramework line from your podfile
  2. Run pod update and it will be removed
  3. Re-add the XCFramework line
  4. Run pod update again

Carthage

The Carthage team has not yet shipped XCFramework support.  On their issues page they say even when available it will not support binary XCFrameworks.  Therefore, you should expect that Carthage will never support SDKs as XCFrameworks.

For developers who use Carthage, you probably will need to install XCFrameworks manually.

Summing it all up

If you recently started having trouble integrating binary frameworks, you are not alone. The shift from Xcode 11 to Xcode 12 has a breaking change regarding XCFrameworks and previous lipo binary frameworks. Apple is requiring the future of binary frameworks to be XCFramework. If you are a vendor, this means you should prioritize making the transition. We don’t know the exact date Apple will enforce Xcode 12 for code signing, but when they do, developers and vendors need to be prepared.

How Embrace helps mobile teams

Embrace is a data driven toolset to help mobile engineers build better experiences. We are a one-stop shop for your mobile app’s needs, including error debugging and monitoring performance and feature releases.

Want to see how Embrace can help your team grow your app with best-in-class tooling and world-class support? Request a demo and see how we help teams set and exceed the KPIs that matter for their business!