Everything wrong with XCFrameworks
Good old frameworks are dated back to time where there was only one platfrom and one language - Obj-C. Today we have multiple version of Swift and different platforms to support.
When XCFrameworks was introduced in last years WWDC I was really excited:
"XCFrameworks make it possible to bundle a binary framework or library for multiple platforms —including iOS devices, iOS simulators, and UIKit for Mac — into a single distributable .xcframework bundle that your developers can use within their own applications." 😲
— Kamil Pyć (@KamilPyc) June 3, 2019
I would like to share my experience with it and what roadblocks you can hit trying to replace Frameworks with XCFrameworks in you project.
Note: all of code and examples was run on Xcode 11.4.1. using Swift 5.2.
Creating XCFrameworks
Lets start from the begining - how to create XCFramework. With Frameworks that just a normal target workflow as you may know from creating app or test targets. Just select “New…” and pick Framework as target.
I would expect that from this point one can select which architectures to include and then export .xcframework
. But it doesn’t work this way.
In order to produce xcframework we need to export all of the frameworks and then combine them with -create-xcframework
command.
You can check out full create script on Github created by Boris Bielik - it’s over 100 lines of code. Update: Boris created convinient Fastlane plugin that wraps this boilerplate up nicely.
I think that this over complication can be one of the reasons why XCFrameworks are still not supported by Carthage.
Xcode
On a first glance usage isn’t different than how we work with standard frameworks. Just add it to target’s “Frameworks and Libraries” section in general tab.
After building app in build log we can find out that XCFrameworks get different treatment than Frameworks.
There is a special ProcessXCFrameworkLibrary
step that’s extracts correct .framework
from all architectures contained in xcframework bundle based on target architecture.
Because of that Xcode need to have explicit list of xcframeworks to process and FRAMEWORK_SEARCH_PATHS
does not work.
Xcode wants to optimise the usage of ProcessXCFrameworkLibrary
and ther is only one call per single xcframework
. That’s will make build to fail in pretty common usage case.
Let’s say we have target ModelA.framework
and ModelB.framework
that links same ModelCore.xcframework
. Xcode selects one of this targets to include the ProcessXCFrameworkLibrary
step and if this selected target will start after another target utilising same xcframework build will simply fail with Framework not found ModelCore
. I took me some time to figure out this race going on when my builds fails randomly 50% of time.
WWDC2020 update: Xcode 12 fixes the race condition bug:
Swift
In order to produce Swift libary that’s support module stability, frameworks needs to be created with BUILD_LIBRARY_FOR_DISTRIBUTION
turned on:
Under the hood this will pass -enable-library-evolution
flag to Swift Compiler. That have couple of implications:
- Code is compiled differently. When I tried to replace Apollo framework with precompiled xcframework version app started crashing with
swift_getEnumCaseMultiPayload.cold.1
- If you have any enums you either have to mark them
@frozen
or handle default case on usage. Unfortunalty that make it impossible to simply replacexcframework
with.framework
in case any code needs to be debugged or profiled. - Generated
.swiftinterface
file is corrupted. This was is already reported but not solved, so you cannot build SwiftyJSON or RxSwift as xcframework.
Wrap up
In summary, there are few things that can block developers from using XCFrameworks expecially with Swift projects. That could explain why they are not widely popular on iOS. We are one month ahead of WWDC and I hope those get issues got resolved, because XCFrameworks are great step ahead of Framework.