This is a demonstration of creating and integrating the xcframeworks and their co-op with static libraries and Swift packages within the same Xcode project.
What's new | Xcode version | Swift version | Description |
---|---|---|---|
Module stable binaries | Xcode 11 | Swift 5.1 | Library evolution allows the library authors to distribute module stable Swift binaries |
New version of .swiftinterface interface | Xcode 11.4 | Swift 5.2 | New annotations added to Swift @_inheritsConvenienceInitializers , @_hasMissingDesignatedInitializers . ⚠️ Module interfaces aren't backwards compatible, clients using Swift 5.1.3 and below won't be able to compile Swift binaries compiled with Swift 5.2. More info here |
Support for binary dependencies in SwiftPM | Xcode 12.0 | Swift 5.3 | Swift Package Manager now supports declaring binary targets in Package.swift |
Debug symbols | Xcode 12.0 | Swift 5.3 | Debug symbols (dSYMs, BCSymbolMaps) can be included within the xcframework through new -debug-symbols <absolute path> flag |
introduce standard format to gain module stability for your Swift frameworks & libraries. Library author & client of a library are no longer required to use the same version of compiler. Please note, that the module stable interfaces are only forward-compatible
provide seamless experience when creating & integrating the module stable frameworks
support all Apple platforms and architectures
STOP creating & using fat dynamic frameworks
. (library author)
STOP slicing frameworks by stripping the architectures in your projects' targets' custom build-phase
. (integrator)
This format bundles module-stable frameworks (.swiftinterface) for the platforms of interest.
The Info.plist contains all available frameworks in a bundle specified by library identifiers. This information is used by Xcode during the linking time => xcodebuild picks the right framework for the platform we're building against. Since Xcode 12.0 the xcframework can contain also debug symbols (dSYMs, BCSymbolMaps).
The structure of xcframework looks as shown below
During my tests I realized, the size of an xcframework
was smaller than the size of an corresponding fat framework
. I tested swift only & mixed frameworks.
Generally the lipo
commandline tool adds a bit of overhead for all contained architectures.
xcframework supports all Apple platforms & their variants - iOS
, maccatalyst
, macOS
, tvOS
, watchOS
, iPadOS
, carPlayOS
.
Platform | Destination |
---|---|
iOS | generic/platform=iOS |
iOS Simulator | generic/platform=iOS Simulator |
maccatalyst | generic/platform=macOS,variant=Mac Catalyst |
iPadOS | generic/platform=iPadOS |
iPadOS Simulator | generic/platform=iPadOS Simulator |
macOS | generic/platform=macOS |
tvOS | generic/platform=tvOS |
watchOS | generic/platform=watchOS |
watchOS Simulator | generic/platform=watchOS Simulator |
carPlayOS | generic/platform=carPlayOS |
carPlayOS Simulator | generic/platform=carPlayOS Simulator |
This section describes the process of creating the xcframework by archiving & creating the final xcframeworks from 2 archives built for iOS
& iOS Simulator
.
However, if you're not interested in the details of the process of how
the xcframework is created, head directly to section: Create xcframework using fastlane plugin.
1.1 Pass SKIP_INSTALL=NO
&& BUILD_LIBRARY_FOR_DISTRIBUTION=YES
to archive your scheme
xcodebuild archive \
-workspace MyWorkspace.xcworkspace \
-scheme MyScheme \
-destination "generic/platform=iOS" \
-archivePath "archives/MyScheme-iOS" \
SKIP_INSTALL=NO \
BUILD_LIBRARY_FOR_DISTRIBUTION=YES
1.2 iOS Simulator - archive your scheme for iOS Simulator platform by specifying correct destination destination="generic/platform=iOS Simulator"
& point archivePath to architecture specific path, e.g. archives/MyScheme-iOS-Simulator
.
xcodebuild archive \
..
-destination "generic/platform=iOS Simulator" \
-archivePath "archives/MyScheme-iOS-Simulator" \
..
1.3 iOS - archive your scheme for iOS by specifying destination="generic/platform=iOS"
& point archivePath to device specific path. The architecture specific path will ensure the archive from step 2. wont be overwritten, e.g. MyScheme-iOS
xcodebuild archive \
..
-destination "generic/platform=iOS" \
-archivePath "archives/MyScheme-iOS" \
..
Binaries in .xcarchive
are located under:
Products/Library/Frameworks
folder for dynamic frameworksProducts/usr/local/lib
folder for static librariesxcodebuild
allows you to create xcframework by specifying frameworks, libraries or even can add headers to the libraries.
-output
argument. Don't forget to add .xcframework
extension to your output path.xcodebuild -create-xcframework \
-framework My-iOS.framework \
-debug-symbols <absolute path to dSYM or BCSymbolMaps folder in the xcarchive> # available from XCode 12.0+
-framework My-iOS_Simulator.framework \
-debug-symbols <absolute path to dSYM or BCSymbolMaps folder in the xcarchive> # available from XCode 12.0+
-output My.xcframework
Module stability is gained with Xcode 11 + Swift 5.1, once your module declares .swiftinterface
file, that describes the public interface of your framework along with linker flags, used toolchain and other info. Swift interface can be found under your framework's swiftmodule
folder.
.swiftinterface
file is autogenerated when xcframework is created.
This plugin allows you to generate the xcframework
(including all dSYMs & BCSymbolMaps) by specifying the desired destinations.
E.g. destination [iOS]
will generate xcframework that contains slices for both iOS
& iOS Simulator
.
⚠️ Currently the plugin doesn't support static libraries.
Add plugin to your project
fastlane add_plugin create_xcframework
Add lane to your Fastfile
desc "Export xcframework"
lane :export_xcframework do
create_xcframework(
workspace: 'path/to/your.xcworkspace',
scheme: 'name of your scheme',
destinations: ['iOS', 'maccatalyst'],
xcframework_output_directory: 'Products/xcframeworks'
)
end
NOTE:
Version 1.1.0 of the fastlane plugin includes support for debug symbols
.
You can try out the plugin in this project by calling following command:
bundle exec fastlane export_xcframework
Make sure to always build & run your generated xcframework before distributing it to your clients. Few of the problems will unveil just at the compile or run time, so don't rely solely on the success of the xcframework creation.
Here's the list of compiler errors I got across when integrating built xcframework into Xcode project.
Problem | Severity | Description | Solution |
---|---|---|---|
Redundant conformance of x to NSObjectProtocol |
error - thrown at dynamic linking time | Your class is already subclassed from NSObject , which conforms to NSObjectProtocol |
Check protocol conformances of your classes and remove redundant conformance to NSObjectProtocol |
Use of unimplemented initializer 'init()' for class | error - thrown at dynamic linking time | Objective-C ABI public classes need to provide public init |
Provide public init override for your public class: override public init() |
@objc' class method in extension of subclass of Class X requires iOS 13.0.0 |
error | Rules for interoperability with Objective-C has changed since iOS 13.0.0. and currently doesn't support @objc interoperability in class extensions. There's open question on Swift forums |
Move/Remove @objc declaration from your Swift class extension |
scoped imports are not yet supported in module interfaces | warning | Read more about Swift import declarations here: https://nshipster.com/import/ | Import the module instead of specific declaration. For example: change import class MyModule.MyClass to import MyModule |
Can’t use framework compiled with Swift 5.2 in Swift 5.1.3 project | error - thrown at linking time | The xcframework was generated using the Swift 5.2 and above. Module stable interfaces are not backwards-compatible. | Update your Xcode to Xcode 11.4 and above or generate module stable binary using Xcode 11.3 and below |
Incompatible module | error - thrown at linking time | The module built for iOS Simulator shares the same arch slice as the new M1. | Exclude arm64 slice when building your xcframework by specifying following build setting: EXCLUDED_ARCHS[sdk=iphonesimulator*] = arm64 |
Distribution of xcframeworks using the native dependency manager Swift Package Manager
or the 3rd party dependency managers does come with several reported integration issues. Manual integration seems sofar to be the safest/bullet-proof integration option.
Swift Package Manager
swift package compute-checksum <xcframework filename
.CocoaPods
vendored_frameworks
to specify you xcframework(s) in your podspec. e.g. spec.vendored_frameworks = 'DynamicFramework.xcframework'
spec.preserve_paths = [...]
, since Xcode 12 the xcframeworks can contain symbol files, so there's no need to distribute the symbol files explicitlyCarthage
--use-xcframeworks
flag is passed. It doesn't support fetching already existing xcframeworks.XCFrameworks
workspace consists of:
StaticLibrary
project - represents static library project
DynamicFramework
project - represents project that builds dylib
Swift Package
- Swift Package for internal development (within Sample project)
TextAttributes
- external Swift Package
Sample
- Sample project that includes all of the dependencies mentioned above.
https://developer.apple.com/videos/play/wwdc2019/416/
https://developer.apple.com/wwdc20/10147 https://developer.apple.com/documentation/swift_packages/distributing_binary_frameworks_as_swift_packages
https://swift.org/blog/abi-stability-and-more/
https://swift.org/blog/library-evolution/
https://github.com/apple/swift-evolution/blob/master/proposals/0260-library-evolution.md
https://github.com/apple/swift/blob/master/docs/LibraryEvolution.rst
https://spec.fm/podcasts/swift-unwrapped/308610
https://gankra.github.io/blah/swift-abi/