Skip to Content
Course content

101: Binary Targets and XCFrameworks

Click on the "Edit" button in the top corner of the screen to edit your slide content.

Let's talk about how you actually ship code to other people—or even other teams within your own company—when you aren't just handing over a git repository. I've seen a lot of developers start by simply sharing source code via a Swift Package, which is great for open source. But in a professional setting, you'll eventually hit a wall where you can't (or shouldn't) expose your source. Maybe you're using a proprietary C++ engine for something like a ImageProcessingKit, or perhaps you just want to slash the build times for a massive internal library that rarely changes.

The "Just Send the Framework" Trap

The most intuitive, and usually wrong, approach is to build your project as a framework, grab the resulting .framework bundle from your build folder, and send it over. On the surface, it works. Your colleague drags the folder into their project, and suddenly they have access to your classes and methods. But this approach assumes a world that doesn't exist anymore: a world where every developer is using the exact same hardware and target.

If I build a framework on my M2 MacBook for a physical iPhone, that binary is compiled for arm64. If you try to run that in an iOS Simulator on an Intel Mac, it'll crash instantly with a "missing architecture" error. You might think, "I'll just make a fat binary using lipo," which was the industry standard for years. You'd essentially stitch the simulator binary and the device binary together into one file. But that's a fragile process, and since the transition to Apple Silicon, the simulator and the device often both use arm64, meaning lipo can't actually distinguish between them anymore. You'll hit a collision, and the build will fail.

The XCFramework Solution

This is why Apple introduced XCFrameworks. Instead of trying to smash multiple architectures into a single binary file, an .xcframework is essentially a directory that acts as a wrapper. Inside, it holds separate .framework bundles for every single platform and architecture you support—iOS devices, iOS simulators, macOS, watchOS, and so on.

When you use an XCFramework, Xcode stops guessing. It looks at the target you're currently building for and says, "Okay, I'm building for an arm64 simulator; I'll pull the binary from the simulator folder," rather than trying to parse a monolithic file. I personally find this much cleaner because it decouples the delivery of the code from the configuration of the consumer's project. You ship the whole bundle, and Xcode handles the slicing automatically.

The Trade-off: Binary vs. Source

You might be wondering why you'd ever do this instead of just using a standard Swift Package with source code. The trade-off is fundamentally about control and speed. When you distribute as a binary target, you're providing the pre-compiled machine code. The consumer doesn't have to compile your 50,000 lines of ImageProcessingKit every time they clean their build folder; they just link against the binary. It's a massive win for build performance in large organizations.

However, you lose some flexibility. If your binary was compiled with Swift 5.9 and your consumer is using Swift 6.0, you might run into module compatibility issues (though "Module Stability" settings in Xcode help mitigate this). You're also making debugging harder for the user; unless you ship separate .dSYM files, they can't step into your code—they'll just see assembly instructions. For me, the rule of thumb is simple: use source for everything internal and open, and XCFrameworks for third-party dependencies or massive, stable libraries that are slowing down the team's CI pipeline.

# The conceptual flow for creating one:
# 1. Archive for iOS Device
xcodebuild archive -scheme ImageProcessingKit -destination "generic/platform=iOS" ...

# 2. Archive for iOS Simulator
xcodebuild archive -scheme ImageProcessingKit -destination "generic/platform=iOS Simulator" ...

# 3. Bundle them together
xcodebuild -create-xcframework \
    -framework ./build/ios.framework \
    -framework ./build/ios_sim.framework \
    -output ./ImageProcessingKit.xcframework



📋 Practical Task

Packaging ImageProcessingKit for Multi-Platform Distribution

You have been tasked with distributing a proprietary library called ImageProcessingKit. Currently, the team is sharing the source code, but it's causing the main app's build time to increase by three minutes per compile. You need to convert this into a binary delivery system.

Your Goal: Create a distribution plan and a set of commands to move from source-based distribution to an XCFramework.

  • Step 1: Identify the two primary architectures you must support to ensure the library works for both physical iPhone testing and Xcode Simulator testing on Apple Silicon.
  • Step 2: Write out the xcodebuild command sequence required to archive the framework for both of those targets.
  • Step 3: Write the final -create-xcframework command that merges those two archives into a single ImageProcessingKit.xcframework.
  • Step 4: Explain in one sentence how you would integrate this resulting .xcframework into a Package.swift file using the .binaryTarget declaration.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.