Overview
In short, I'm observing a runtime crash, in a Swift Package with debug compiler directives (#if DEBUG
) that is shared between an XCFramework and an App.
The framework only links to the Swift Package and the App embeds both. The issue occurs when the framework is compiled in Release, but the App in Debug configuration.
This issue has forced me to distribute both Release and Debug artefacts, which is really cumbersome for my users, especially when integrating my XCFramework with Swift Package Manager.
Reproduction
I have created a sample project on GitHub that provides a setup to reproduce this issue. You can clone it, open MyApp.xcodeproj
and just run the MyApp Target.
The project MyApp consists of:
- an App Target called MyApp
- a static library called MyLibrary
- a local Swift Package called MyPackage
- an aggregated target MyFramework
- Creates an XCFramework of MyLibrary
- a 2nd local Swift Package MyFramework
- Serves "MyLibrary.xcframework" to MyApp as a Package
The Dependency Graph looks like this:
MyApp
└── embeds MyFramework (MyLibrary.xcframework)
└── links to MyPackage
└── embeds MyPackage
If we compile MyFramework in release configuration and then build MyApp using the XCFramework, the following crash be witnessed:
Thread 0 Crashed:: Dispatch queue: com.apple.main-thread
0 libswiftCore.dylib 0x192cd786c swift::RefCounts<swift::RefCountBitsT<(swift::RefCountInlinedness)1>>::incrementSlow(swift::RefCountBitsT<(swift::RefCountInlinedness)1>, unsigned int) + 52
1 MyApp 0x1022ce050 HelloPackage.hello() + 164 (MyPackage.swift:14)
2 MyApp 0x1022cd1d0 closure #1 in ContentView.body.getter + 44 (ContentView.swift:14)
3 SwiftUI 0x1c49e38b8 0x1c4371000 + 6760632
4 MyApp 0x1022ccf14 ContentView.body.getter + 92 (ContentView.swift:13)
5 MyApp 0x1022cd6f4 protocol witness for View.body.getter in conformance ContentView + 12
The Source File in question inside MyPackage is the following:
public struct HelloPackage {
private let message: String = "MyPackage"
#if DEBUG
private let debug: String = "Debug"
#endif
public init() {}
public func hello() -> String {
#if DEBUG
"\(debug) \(message)" # << CRASH OCCURS HERE
#else
"Release \(message)"
#endif
}
}
Note here, that all #if DEBUG
directives are covering private
properties or are inside method implementations. Nevertheless, running this in the App in the Debug configuration creates a crash, as if let debug
was not initialised or doesn't exist. But shouldn't it have been compiled again, as it's not part of the XCFramework?
Conclusion
- Should Compiler Directives like
#if DEBUG
not be distributed inpublic
Source Files of a Swift Package and are known to cause crashes when linked to binary frameworks? - What kind of references to a Swift Package dependency are compiled into a static library by Xcode, when the Package is only added via Target Dependencies, but not to Frameworks, Libraries and Embedded Content?
- Or is there some step missing in my XCFramework creation? Do I need to remove something from the binary?
# Build Simulator Framework
xcodebuild archive -project MyApp.xcodeproj \
-scheme MyLibrary \
-configuration ${CONFIGURATION} \
-destination "generic/platform=iOS Simulator" \
-archivePath ${SRCROOT}/build/${CONFIGURATION}-iphonesimulator \
SKIP_INSTALL=NO
# Create XCFramework
xcodebuild -create-xcframework \
-library ${SRCROOT}/build/${CONFIGURATION}-iphonesimulator.xcarchive/Products/usr/local/lib/libMyLibrary.a \
-output ${SRCROOT}/build/MyLibrary.xcframework
# Copy Modules
cp -R ${OBJROOT}/ArchiveIntermediates/MyLibrary/BuildProductsPath/${CONFIGURATION}-iphonesimulator/MyLibrary.swiftmodule ${SRCROOT}/build/MyLibrary.xcframework/ios-arm64_x86_64-simulator