🥧 What is with iOS simulator build built from Xcode 12?
If we build with xcodebuild, and specify the generic destination:
xcodebuild -project temp.xcodeproj -scheme temp -destination "generic/platform=iOS Simulator"
and then we run this command:
lipo -archs temp.app/temp
we get:
x86_64 arm64
If we build the same thing within the Xcode < 12 application, specifying a particular simulator model, we instead get this:
x86_64
Xcode 12 includes a slice for the arm64 architecture when building for the simulator. This is in addition to the usual i386 and x86_64 architectures for Xcode < 12.
🤔 Why so?
Looks like they’re thinking ahead to ARM-based Macs.
⚠️ What problems does it cause?
Let’s say we have a pre-built framework, ready for the simulator and any iOS device. When we try to link that framework under the above command in Xcode 12, we’ll get an odd-sounding error, something like this:
building for iOS Simulator, but linking in object file built for iOS, for architecture arm64
It’s looking for the arm64 slice, and it found it! But because it’s categorized as for device, instead of for the simulator, the linker errors out.
You might say to yourself, I can fix this! I’ll rebuild my framework using Xcode 12. But it’s not true.
We make a framework for shipping with lipo. We merge multiple architectures into a single binary also known as Fat Frameworks. But when we try to use lipo -create to combine (a) a device binary with ARM slices and (b) a simulator binary with ARM and Intel slices, we get an error:
lipo: simulator/sample.framework/sample and devices/sample.framework/sample have the same architectures (arm64) and can't be in the same fat output file
As our device build also includes the arm64 architecture, lipo does not know which of the two arm64 slices we want and refuses to create a combined fat binary framework.
🔧 How can we solve this?
Well, it depends on how we want to ship our SDK. If we want to keep shipping our SDK as framework then a workaround to build framework using Xcode 12 are -
Either exclude the arm64 architecture from the simulator build by appending the EXCLUDED_ARCHS build variable:
xcodebuild -target SampleSDK ONLY_ACTIVE_ARCH=NO -configuration ${CONFIGURATION} -sdk iphonesimulator BUILD_DIR="${BUILD_DIR}" BUILD_ROOT="${BUILD_ROOT}" OBJROOT="${OBJROOT}/DependentBuilds" EXCLUDED_ARCHS="arm64"
Or use lipo -remove to remove the arm64 architecture from the simulator build before combining the simulator and device frameworks into one:
lipo -remove arm64 "$BUILD_DIR/${CONFIGURATION}-iphonesimulator/${PRODUCT_NAME}/${PROJECT_NAME}" -output "$BUILD_DIR/${CONFIGURATION}-iphonesimulator/${PRODUCT_NAME}/${PROJECT_NAME}"
Does this solve lipo issue? Yes. But again since we have excluded arm64 architecture for simulator build. While testing our SDK on simulator built with Xcode 12 will keep throwing error.
One of the solution to it is distribute framework as XCFrameworks. It allows developers to conveniently distribute binary libraries for multiple platforms and architectures in a single bundle. This means we no longer need to merge multiple architectures into a single binary.