iOS Dependency Formats Deep Dive

Updated Jun 01, 2026#ios#xcode#swiftpm#xcframework

An iOS dependency can arrive as source code, a prebuilt binary, or a package that wraps either one. These concepts overlap, but they are not interchangeable:

  • A Swift package is a distribution and dependency-management mechanism.
  • A framework is a bundle that can contain compiled code, resources, headers, and module metadata.
  • An XCFramework is a container for multiple platform-specific variants of a framework or library.
  • A static library becomes part of the client executable at link time.
  • A dynamic library is loaded separately at runtime.

For most reusable code, start with a source-based Swift package. Use an XCFramework when you need to ship a prebuilt binary, usually for closed-source SDKs, build-time reduction, or code produced by another build system.

Format Overview

Format Contains Best fit
Swift package Manifest, source targets, resources, or binary targets Default distribution method for reusable source code
XCFramework Platform and variant-specific frameworks or libraries Distributing a binary across Apple platforms and Simulator builds
Framework One platform-specific static or dynamic framework bundle Build product inside an XCFramework or direct project dependency
Static library (.a) Archive of object files C-family binaries, usually packaged inside an XCFramework
Dynamic library (.dylib) Runtime-loaded shared library Primarily macOS; use framework bundles for dynamic linking on iOS

Swift Packages

A Swift package is defined by a Package.swift manifest. It organizes code into targets and exposes products to clients. A package can include:

  • Swift, Objective-C, Objective-C++, C, or C++ targets
  • Resources scoped to a target
  • Executable and plugin targets
  • Dependencies on other packages
  • Local or remote binary targets on Apple platforms

A target defines a module. A library product exposes one or more target modules to clients.

// swift-tools-version:6.0
import PackageDescription

let package = Package(
  name: "AnalyticsKit",
  platforms: [.iOS(.v17), .macOS(.v14)],
  products: [
    .library(name: "AnalyticsKit", targets: ["AnalyticsKit"])
  ],
  targets: [
    .target(
      name: "AnalyticsKit",
      resources: [.process("Resources")]
    ),
    .testTarget(
      name: "AnalyticsKitTests",
      dependencies: ["AnalyticsKit"]
    )
  ]
)

Use a source package when consumers can compile the code themselves. This is usually the simplest option:

  • Consumers can inspect and debug the source.
  • The client toolchain compiles the code for its selected platform.
  • You do not need to build and upload a binary for every supported platform variant.
  • Swift compiler compatibility is easier because the dependency is rebuilt with the client.

Static and Dynamic Package Products

A SwiftPM library product can leave the linkage type unspecified or explicitly request .static or .dynamic:

products: [
  .library(name: "AnalyticsKit", targets: ["AnalyticsKit"]),
  .library(name: "AnalyticsKitStatic", type: .static, targets: ["AnalyticsKit"]),
  .library(name: "AnalyticsKitDynamic", type: .dynamic, targets: ["AnalyticsKit"])
]

Do not force a linkage type unless you have a concrete reason. Letting the build system choose gives clients more flexibility.

Binary Targets

SwiftPM can wrap a local or remote XCFramework in a binary target. Remote archives require an HTTPS URL and checksum:

// swift-tools-version:6.0
import PackageDescription

let package = Package(
  name: "AnalyticsKit",
  products: [
    .library(name: "AnalyticsKit", targets: ["AnalyticsKit"])
  ],
  targets: [
    .binaryTarget(
      name: "AnalyticsKit",
      url: "https://example.com/AnalyticsKit.xcframework.zip",
      checksum: "<sha256>"
    )
  ]
)

Generate the checksum after creating the ZIP archive:

swift package compute-checksum AnalyticsKit.xcframework.zip

The XCFramework must be at the root of the ZIP archive. For a local artifact, use .binaryTarget(name:path:) and point directly to the .xcframework.

Binary targets are only available on Apple platforms. A package can combine source and binary targets, which is useful when a small source wrapper exposes a friendlier API around a closed-source SDK.

XCFrameworks

An XCFramework (.xcframework) is a binary distribution bundle created by Xcode. It groups compatible variants without pretending that device and Simulator binaries are interchangeable.

One XCFramework can contain variants for:

  • iOS and iOS Simulator
  • macOS and Mac Catalyst
  • iPadOS
  • tvOS
  • visionOS
  • watchOS
  • DriverKit

Each variant can contain a static or dynamic framework. An XCFramework can also contain static libraries and headers.

Why XCFrameworks Replaced Fat Framework Workarounds

A universal, or fat, binary combines architecture slices for one platform variant. For example, a Simulator static library can contain both arm64 and x86_64.

Do not use lipo to merge an iOS device binary and an iOS Simulator binary. They target different platform variants even when both use arm64. XCFrameworks keep those variants separate and let Xcode select the correct one.

Create an XCFramework

For a Swift framework distributed as a binary:

  1. Set Build Libraries for Distribution (BUILD_LIBRARY_FOR_DISTRIBUTION) to YES.
  2. Set Skip Install (SKIP_INSTALL) to NO.
  3. Archive the framework once for each supported destination.
  4. Combine the archives with xcodebuild -create-xcframework.
xcodebuild archive \
  -project AnalyticsKit.xcodeproj \
  -scheme AnalyticsKit \
  -destination "generic/platform=iOS" \
  -archivePath "archives/AnalyticsKit-iOS"

xcodebuild archive \
  -project AnalyticsKit.xcodeproj \
  -scheme AnalyticsKit \
  -destination "generic/platform=iOS Simulator" \
  -archivePath "archives/AnalyticsKit-Simulator"

xcodebuild -create-xcframework \
  -archive "archives/AnalyticsKit-iOS.xcarchive" \
  -framework "AnalyticsKit.framework" \
  -archive "archives/AnalyticsKit-Simulator.xcarchive" \
  -framework "AnalyticsKit.framework" \
  -output "AnalyticsKit.xcframework"

For a static C-family library, use -library and provide headers:

xcodebuild -create-xcframework \
  -library "products/ios/libParser.a" \
  -headers "products/ios/include" \
  -library "products/simulator/libParser.a" \
  -headers "products/simulator/include" \
  -output "Parser.xcframework"

Rebuild binary SDKs as platforms adopt new architectures. Clients can only build destinations covered by the slices included in the artifact.

Sign and Verify XCFrameworks

Sign an XCFramework before distribution:

codesign --timestamp -s "<identity>" AnalyticsKit.xcframework

Xcode can inspect the signing identity of an XCFramework and fails the build if the signature is removed, becomes invalid, or changes unexpectedly. Treat third-party binary SDK updates as supply-chain changes: verify the signer and review release provenance.

Include a Privacy Manifest

A third-party SDK can include a PrivacyInfo.xcprivacy file that describes data collection, tracking domains, and use of required-reason APIs. Xcode combines dependency manifests into a privacy report when preparing an app for distribution.

Include a privacy manifest when your SDK uses required-reason APIs, collects data, enables an app to collect data, or contacts tracking domains. Apple also requires manifests for SDKs on its commonly used third-party SDK list. Listed SDKs distributed as binary dependencies require signatures as well.

Add PrivacyInfo.xcprivacy to the SDK target’s resources so Xcode places it correctly in framework and XCFramework products.

Frameworks

A framework (.framework) is a bundle for one platform-specific build product. It can contain:

  • Compiled code
  • Resources
  • Public headers
  • A module map or Swift module metadata
  • An Info.plist
  • A privacy manifest when needed

Frameworks can be dynamic or static.

Dynamic Frameworks

A dynamic framework remains a separate binary at runtime. The app links against it and embeds it in the app bundle. This can help share code between an app and its extensions, but it adds launch-time and packaging considerations.

For iOS, iPadOS, tvOS, visionOS, and watchOS, dynamic linking requires framework bundles. Do not distribute bare .dylib files for those platforms.

Static Frameworks

A static framework contains a static archive as its main binary. The linker copies the needed code into the client executable. Starting with Xcode 15, Apple supports static frameworks that bundle resources with a static library.

For distribution, archive each supported platform variant and package the variants into an XCFramework. Do not manually place a .a file inside a .framework directory and call it a static framework.

Static Libraries

A static library (.a) is an archive of object files. At link time, the linker copies required code into the client executable.

Static libraries are common for C and C++ dependencies. They do not bundle resources by themselves. When distributing a static library across Apple platforms, put each platform variant and its headers into an XCFramework.

Parser.xcframework/
  ios-arm64/
    libParser.a
    Headers/
  ios-arm64_x86_64-simulator/
    libParser.a
    Headers/

Expose C-family APIs to Swift with public headers and a module map. A SwiftPM C-family target can use publicHeadersPath; a system-library target can describe an installed library with module.modulemap and pkg-config.

Dynamic Libraries

A dynamic library (.dylib) remains separate from the executable and is loaded at runtime. Apple operating systems ship system dynamic libraries, which allow apps to use OS-provided implementations without embedding copies.

For third-party code on iOS-family platforms, distribute dynamically linked code in a framework bundle, usually wrapped in an XCFramework. Bare third-party .dylib distribution is mainly relevant to macOS.

Swift Binary Compatibility

Source packages are compiled with the client’s toolchain. Binary Swift frameworks need additional compatibility planning.

Set BUILD_LIBRARY_FOR_DISTRIBUTION=YES when creating a Swift binary framework for distribution. Xcode generates a textual .swiftinterface and enables library evolution support so clients can import the framework from compatible Swift compiler versions.

This setting has tradeoffs:

  • Public API changes require compatibility discipline.
  • Exposing implementation details in public types makes evolution harder.
  • A binary still supports only the platforms and architectures included in its XCFramework.
  • You should test the release artifact in a separate client app, not only the framework project.

Link, Embed, and Resources

The correct integration settings depend on linkage:

Dependency Link Embed in app bundle
Static library Yes No
Static framework Yes Let Xcode handle its resource bundle behavior
Dynamic framework Yes Yes, normally Embed & Sign
XCFramework Depends on the selected contained variant Let Xcode select and integrate the matching variant
Source Swift package Managed by SwiftPM and Xcode Depends on the resolved product linkage

Avoid embedding the same dynamic framework independently in multiple nested bundles. If an app and extension both use a dynamic framework, verify the final archive layout and signing rather than relying on Debug builds alone.

Choosing a Format

Situation Recommendation
Reusable app feature or open-source library Source-based Swift package
Internal source code shared across apps Private Swift package
Closed-source Swift or Objective-C SDK Signed XCFramework distributed through a SwiftPM binary target
Cross-platform C or C++ library Static libraries and headers packaged as an XCFramework
App-specific modularization Swift package or Xcode framework target, based on build and ownership needs
Existing fat framework script Replace it with per-destination archives and an XCFramework
Bare .dylib intended for iOS Package dynamic code as a framework, then distribute an XCFramework

Release Checklist

Before publishing a binary SDK:

  1. Archive every supported platform and Simulator variant.
  2. Enable BUILD_LIBRARY_FOR_DISTRIBUTION for Swift binary frameworks.
  3. Build the XCFramework with xcodebuild -create-xcframework.
  4. Include and validate PrivacyInfo.xcprivacy when your SDK needs a privacy manifest.
  5. Sign the XCFramework and verify the signing identity.
  6. ZIP the XCFramework at the archive root for remote SwiftPM distribution.
  7. Generate the SwiftPM checksum from the final ZIP archive.
  8. Test the release artifact in a clean client app for every supported destination.
  9. Inspect the final app archive for duplicate embeddings, missing resources, and signing problems.