Preface
When you build an app, you build it for a specific 'platform' and 'CPU Architecture' combination.
iOS Platforms:
- iOS device
"generic/platform=iOS"
- Simulator
"generic/platform=iOS Simulator"
Note: There a whole lot more platforms. For brevity I’m just focused on iOS platforms. Otherwise there are macOS, watchOS, tvOS, visionOS platforms.
CPU Architectures:
This is because a macOS vs iOS vs iOS simulator will use different libraries. See How does building for iOS device and simulator actually differ?
And different CPU Architectures use different CPU instructions.
This ultimately means the built binary and the environment (device/simulator) you're running it in, will need to match. Otherwise some system libraries won't exist on the operating system, or the CPU instructions can't get processed.
Where are these settings and who changes them?
The configuration for this is in Xcode Build Settings. They affect the flags sent to the compiler. Usually Xcode does things correct and you don’t need to make any adjustments. In certain cases it doesn’t or someone (app creator, library creator, library consumer, package manager i.e. someone who makes changes into the Podfile) has tweaked these settings in a way that things get misaligned.
Will you always be building for one combination?
No. If you're using a Release
config, then you'll end up archiving your product for BOTH arm64
and x86_64
architectures.
During the archive process (often in your CI/CD) then things will fail because the simulator architectures are selected as well.
Yet a 'Debug' builds onto your physical devices won't attempt to build for a different platform or architecture. It's because it prioritizes 'development speed' over 'complete correctness'.
The default Xcode ONLY_ACTIVE_ARCH
settings for this are:
I have an Intel MacBook. If I don't exclude any architecture and platform, what will happen?
If you're building a debug config, then it will only build for the CPU architecture of the destination's platform. Meaning:
- If you're building into your iPhone, you'll just need to support ARM64
- If you're building into a simulator, you'll just need to support X86_64
If you're building a release config, then it will:
- Build for both platforms and all possible architectures.
I have an M1 MacBook. If I don't exclude any architecture and platform, what will happen?
If you're building a debug config, then it will only build for the CPU architecture of the destination's platform. Meaning:
- If you're building into your iPhone, you'll just need to support ARM64
- If you're building into a simulator, you'll just need to support ARM64
- Alternatively you can enable Rosetta for Xcode. In that case if you're building into a simulator, then you need to support X86_64 as well.
If you're building a release config, then it will:
- Build for both platforms and all possible architectures.
Why is it different for Release builds?
During active development, you want to build things fast. It doesn't make sense for you to actively build architectures you don't need to use. That means if you're building into a device, then you only want to build for that platform-architecture combo. You don't want to build the other combinations.
However if you're releasing it — for others. Then because you don't want to limit/dictate how they build your app, then you have to build for all possible combinations and make sure your framework compiles for all of them.
For an app it may make less sense to make an archive for the simulator platform, however for a framework, your consumer is another developer. That developer will be building your framework into a simulator and into a real device. Hence you need to support both platforms and architectures.
Is CocoaPods the source of the problem?
It depends. If the pod has excluded a certain architecture, and you're trying to build for that architecture, then you have to ask the pod owner to not excluded it.
Otherwise if all of your pods are supporting the given platform/architecture that you're trying to build for, then it's a problem from within the code you you wrote yourself in your host app.
Solution
Update pre-compiled libraries with Apple silicon support
If the library named in the error message is from a vendor, see
Update pre-compiled libraries from
vendors. If you have source code for the library, rebuild the library as an
XCFramework with support for the simulator on Apple silicon. To learn
how to build an XCFramework, see Creating a multiplatform binary
framework
bundle.
Update pre-compiled libraries from vendors
If the library producing the build error is a pre-compiled library
from a vendor and you don’t have the source code, contact the vendor
for an updated XCFramework supporting Apple silicon. If an update
isn’t available from the vendor, temporarily use the EXCLUDED_ARCHS
build setting to exclude arm64 for the simulator SDK as shown in the
figure below. Do not exclude arm64 for any other SDK.
From Docs
More Questions in regard to the outcome of the solution:
What happens if I exclude ARM64 for the 'iOS Simulator' Platform?
You won't be able to build into M1 simulators directly. You'd have to Use Xcode with Rosetta on M1 then. Which is a hack and is slightly slower.
What happens if I exclude X86 for the 'iOS Simulator' Platform?
You won't be able to build into Intel based simulators. Nor you'd be able to build into a simulator using Rosetta.
What happens if exclude ARM64 for the 'iOS' Platform?
You won't be able to build into physical devices. Nor archive for them. Terrible idea!
So I should exclude architectures?
Strive for your app, including all of its pre-compiled libraries, to always build for the complete set of architectures defined by the default value of the ARCHS build setting. Only use the EXCLUDED_ARCHS build setting on targets where the final released app is not using the target’s functionality on a specific architecture, such as a Mac app that only supports a legacy feature on Intel-based Mac computers. Do not modify the ARCHS build setting to achieve the same result.
Additionally in big teams, some developers may be on an M1, while some others are on older Intel based MacBooks. You never know maybe some day there'd be an M5 Macbook, that will have a distinct architecture. So it's good to be considerate of how you make your project/product/framework compatible for your own devs and your library consumers.
Do we have an iOS device with Intel X86_64?
Such a thing doesn't exist.
How do I inspect a binary?
You can use either lipo
, file
or dyld_info
.
lipo
and file
only give you architecture information.
dyld_info
gives you architecture, platform information and more
See comparisons a simple inspection on the terminal app binary:
Remember within an XCFramework there will be usually two or more binaries. Example if we just used lipo -info
we have to do it on both binaries of our library:
lipo -info <path-to-binary>
On the arm64 directory within an xcframework packaging,
I see:
Non-fat file: /Users/mfaani/Video.xcframework/ios-arm64/Video.framework/Video is architecture: arm64
For the sims I see:
Architectures in the fat file: /Users/mfaani/Video.xcframework/ios-arm64_x86_64-simulator/Video.framework/Video are: x86_64 arm64
You can go into your mac's /Applications, right click; 'show Package Contents' for any app; find the app's associated binary. And then inspect it using lipo
.
To be clear, a framework or an app can both be inspected with lipo
. Similarly if you access the build folder on the simulator, you can inspect the binary as well.
What's the difference between an XCFramework and a FAT binary?
- A FAT binary, is just a binary — with two (or more) architectures combined into a single binary. It's just named FAT because it's fattened. Its other names are 'multi-architecture binary' or universal binary.
- An XCFramework is just a structured folder, a wrapper. Nothing more. That has distinct folders per platform. Within each folder there's a binary. That binary can be FAT binary or non-FAT (single architecture).
Also note, a FAT binary can be either a framework or the binary of an app. An XCFramework is just a framework. It's never the app itself.
Does my app get bloated with all the other platforms-architecture combinations I don't need?
XCFramework won't bloat app store submissions, because an archive for the iOS device will just pick up each framework from a directory that's isolated from simulator platform.
However with FAT binaries, given that it was just a single binary, you had to thin/slice the dependencies before submitting to Apple store. Otherwise you'd get the following error:
Unsupported Architecture. Your executable contains unsupported architecture '[x86_64, i386]'."
For more on that and its previous solution, see here
To be perfectly clear, this was an issue in pre-xcode 12. But not every project setup would face this. You'd only face this if you had some pre-compiled dependency from a vendor (who didn't share their source code, but just shared the FAT binary so you can build the app for both device and sim), otherwise if you had access to the source code then your archive wouldn't contain compilations for both architectures. It would only contain binaries for targeted architecture.
Also see this great blog post for about some more details and historical context.
What does the Folder structure of a FAT look like?
It's just a binary. It's not a folder. The binary works for two or more architectures.
What does the Folder structure of an xcframework look like?
ios-arm64
- binary
ios-arm64_x86_64-simulator
- (FAT) binary
If an XCFramework is made to work with mac Catalyst then the folder structure would be like:
ios-arm64
- binary
ios-arm64_x86_64-simulator
- (FAT) binary
ios-x86_64-maccatalyst
- binary
For a more comprehensive list of possible platforms. See XCFrameworsk: Demonstration of creating and integrating xcframeworks and their co-op with static libraries and Swift packages
Why doesn't my vendor support ios-arm64-simulator?
It could be for a number of reasons.
- migration effort: Often it's just that when the compiled they didn't have an M1, and it's been a long time since they've compiled and if they compile they have to make some changes. Like you have to understand it's been years that that there wasn't a need for a new architecture-platform combination. So this is new and not everyone understands it. The fact that Swift is now ABI compatible with its previous versions reduce the need for framework owners to recompile their app with every new Swift release. So they could go on years without the need to recompile...
- limitation: The pre-compiled library depends on another pre-compiled library which isn't compiled for arm64-sim.
- The owner of the framework doesn't have an Apple Silicon (arm64) machine. Or the person who knows about those stuff has left the company.
Any last words?
Make sure you look into ALL Project AND Target AND Pod Project AND Pod Target settings. If one of them excludes a certain architecture then you can't build your app for that architecture. Often this could be in your Podfile.
Or if the pod/framework is being compiled in a totally different repo, then the settings that you have set there, are what matters the most.
If you pay attention to the error message you get, it should be easy to navigate your way to the target that doesn't support. Once you identify the target, then you have to identify when/where it gets compiled using the notes in the two paragraphs above.
Get Info
, and check theopen using rosetta
option. Relaunch Xcode or CLIarm64
to excluded architectures (Build Settings) (3) Clean Build Folder (4) Run app