iOS Dependency Formats

Jul 18, 2022iosswiftobjcxcode

When you add a source-based package dependency to your app, you can inspect the source code, contribute bug fixes, and recompile the source code if needed.

A company may prefer to distribute binaries instead of source files to protect intellectual property. A binary dependency is less portable because it can only support platforms that its included binaries support, and binary dependencies are only available for Apple platforms.

If you have a choice between a source-based dependency and a binary dependency, use the source-based dependency if it provides the same functionality.

At the time of writing, you can use dependencies in following formats: Swift packages, XCFrameworks, frameworks, or libraries.

Swift Packages

Swift packages are reusable components of Swift, Objective-C, Objective-C++, C, or C++ code. They can bundle resources, vend their code as binaries, or depend on other packages.

Use Swift packages to bundle executable code, for example a script, as an executable product, or create a package to vend shareable code as a library product.

Packages that vend a library product help promote modularity in your code, make it easy to share code with others, and enable other developers to add functionality to their apps.

Swift packages don’t use .xcodeproj or .xcworkspace but rely on their folder structure and use the package manifest for additional configuration.

Each Swift package requires a Package.swift file in the main directory of the package — referred to as the package manifest. When you create a Swift package, you use the PackageDescription library in the package manifest to list dependencies, configure localized resources, and set other configuration options.

import PackageDescription

let package = Package(
  name: "SlothCreator",
  platforms: [
    .macOS(.v11),
    .iOS(.v14),
    .watchOS(.v7),
    .tvOS(.v13),
  ],
  products: [
    .library(
      name: "SlothCreator",
      targets: ["SlothCreator"]
    )
  ],
  targets: [
    .target(
      name: "SlothCreator",
      resources: [
        .process("Resources/")
      ]
    )
  ]
)

Xcode supports creating and publishing Swift packages, as well as adding, removing, and managing package dependencies. Its support for Swift packages is built on top of the open source Swift Package Manager project.

Make binaries available to other developers by creating Swift packages that include one or more XCFrameworks. This use case is common for packages that contain source code that wraps closed-source binaries.

XCFramework (.xcframework)

An XCFramework is a distributable binary package created by Xcode that contains variants of a framework or library so that it can be used on multiple platforms (iOS, macOS, tvOS, and watchOS), including Simulator builds. An XCFramework can be either static or dynamic and can include headers.

To use a prebuilt XCFramework, link the target to the XCFramework. Xcode ensures that the target can build against the XCFramework’s headers, link against its binary, and embed it for distribution. If your app has multiple targets (such as app extensions) that use the same XCFramework, you should pick one target (usually your app’s target) to embed the XCFramework and the others should link it without embedding.

You can create an XCFramework that contains multiple platform-specific variants to be used by clients on different platforms, including Simulator builds. XCFramework can also contain a macOS variant of your framework built for AppKit and another macOS variant of your framework built for UIKit.

You will still be required to generate archives for different platforms and bundle them up together in single XCFrameworks.

There are few advantages of using XCFrameworks:

  • XCFramework contain variants not only for device and Simulator, but for any of the platforms that Xcode supports: iOS, macOS, tvOS, watchOS.
  • It supports Swift and C-based code.
  • Can bundle up frameworks and static libraries.

Frameworks (.framework)

A hierarchical directory that encapsulates a dynamic library, header files, and resources, such as storyboards, image files, and localized strings, into a single package. Apps using frameworks need to embed the framework in the app’s bundle.

Frameworks supported from iOS 8. Talking about frameworks also worth mentioning umbrella frameworks and universal frameworks (fat frameworks):

  • Umbrella framework: is a framework bundle that contains other frameworks. Umbrella frameworks available for macOS apps, but Apple do not recommend using them. Umbrella frameworks are not supported on iOS, watchOS, or tvOS.

  • Universal framework (fat framework): multi-architecture binary that contains code native to multiple instructions sets and can run on multiple processor types. In short, it contains code compiled for all the platforms which you are going to support. This approach widely used for sharing a private binary. In this way, your framework consumer able work with your framework on a real device and simulator.

If you are building your own static library and using shell scripts to package it in a .framework directory, you need to migrate to building a framework with a dynamic library instead, as this is the correct way to build a framework. Static frameworks are not a supported way of sharing static libraries.

Libraries (.a, .dylib)

Static library (.a) — collection or archive of object files. Static linker collects app compiled source code with library code into a single executable file, which loaded into memory in its entirety at runtime.

Since static library, in its general sense, is a sequence of statements or instructions in a machine code language, this is adding some limitations to create and distribute them:

  • You’ll need to build a library for the same processor architecture as the client code.
  • The library can’t include resource files: images, assets, nibs, strings file and other visual data.

Dynamic libraries (.dylib) - are not copied into executable file, like static libraries. Instead, they are dynamically linked to at load or runtime, when both binary files and the library are in memory.

System iOS and macOS libraries are dynamic. This means that your app will receive improvements from Apple’s updates without new build submission.

Dynamic libraries outside of a framework bundle, which typically have the file extension .dylib, are not supported on iOS, watchOS, or tvOS, except for the system Swift libraries provided by Xcode.