Dependency managers simplify and standardize the process of fetching third-party code and incorporating it into your project. Without this tool, you’d do this by manually copying source code files, dropping in pre-compiled binaries or using a mechanism like Git submodules.
Modern development is accelerated by the use of external dependencies (for better and worse). This is great for allowing you to get more done in less time, but adding dependencies to a project has an associated coordination cost.
In addition to downloading and building the source code for a dependency, that dependency’s own dependencies must be downloaded and built as well, and so on, until the entire dependency graph is satisfied. To complicate matters further, a dependency may specify version requirements, which may have to be reconciled with the version requirements of other modules with the same dependency.
The role of the package manager is to automate the process of downloading and building all of the dependencies for a project, and minimize the coordination costs associated with code reuse.
Dependency hell is the colloquialism for a situation where the graph of dependencies required by a project cannot be met. Common scenarios are:
A good package manager should be designed from the start to minimize the risk of dependency hell, and where this is not possible, to mitigate it and provide tooling so that the user can solve the scenario with a minimum of trouble.
A simple set of rules and requirements that dictate how version numbers are assigned and incremented.
Consider a version format of X.Y.Z (Major.Minor.Patch). Bug fixes not affecting the API increment the patch version, backwards compatible API additions/changes increment the minor version, and backwards incompatible API changes increment the major version.
Under this scheme, version numbers and the way they change convey meaning about the underlying code and what has been modified from one version to the next.
Carefully consider whether you want to add a binary dependency, because doing so comes with drawbacks. For example, 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.
Git submodules allows you to have a folder in your repository be populated from another Git repository. You can either track a specific commit or a branch in the given submodule.
Before you add a repository as a submodule, first check to see if you have a better alternative available, modern languages like Swift have friendly, Git-aware dependency management systems built-in from the start.
Git submodules work well enough for simple cases, but these days there are often better tools available for managing dependencies than what Git submodules can offer.
SwiftPM is a tool built by Apple as part of the Swift project for integrating libraries and frameworks into your Swift apps. It launched in 2015 and gained integration with Xcode 11 in 2019.
The tool directly addresses the challenges of compiling and linking Swift packages, managing dependencies, versioning, and supporting flexible distribution and collaboration models.
A package consists of Swift source files, including the Package.swift
manifest file. The manifest file, or package manifest, defines the package’s name and its contents using the PackageDescription module. A package has one or more targets. Each target specifies a product and may declare one or more dependencies.
// swift-tools-version:4.0
import PackageDescription
let package = Package(
name: "DeckOfPlayingCards",
products: [
.library(name: "DeckOfPlayingCards", targets: ["DeckOfPlayingCards"])
],
dependencies: [
.package(url: "https://github.com/apple/example-package-fisheryates.git", from: "2.0.0"),
.package(url: "https://github.com/apple/example-package-playingcard.git", from: "3.0.0"),
],
targets: [
.target(
name: "DeckOfPlayingCards",
dependencies: ["FisherYates", "PlayingCard"]),
.testTarget(
name: "DeckOfPlayingCardsTests",
dependencies: ["DeckOfPlayingCards"]),
]
)
A package author can publish their Swift package to either public or private repositories. Xcode supports both private and publicly available packages.
Swift organizes code into modules. Each module specifies a namespace and enforces access controls on which parts of that code can be used outside of that module.
A program may have all of its code in a single module, or it may import other modules as dependencies. Aside from the handful of system-provided modules, such as Darwin on OS X or GLibc on Linux, most dependencies require code to be downloaded and built in order to be used.
In Swift 5.5, SwiftPM adds support for package collections — bite size curated lists of packages that make it easy to discover, share and adopt packages.
Package collections embrace and promote the concept of curation. Instead of browsing through long lists of web search results, package collections narrow the selection to a small list of packages from curators you trust.
You can develop your app without ever publishing it in a place where others can see or use it. On the other hand, if one day you decide that your project should be available to a wider audience your sources are already in a form ready to be published.
The package manager is also independent of specific forms of distribution, so you can use it to share code within your personal projects, within your workgroup, team or company, or with the world.
The Swift Package Index is a search engine for packages that support the Swift Package Manager.
But this site isn’t simply a search tool. Choosing the right dependencies is about more than just finding code that does what you need. Are the libraries you’re choosing well maintained? How long have they been in development? Are they well tested? Picking high-quality packages is hard, and the Swift Package Index helps you make better decisions about your dependencies.
CocoaPods manages dependencies for your Xcode projects. It aims to improve the engagement with, and discoverability of, third party open-source Cocoa libraries.
CocoaPods is built with Ruby and it will be installable with the default Ruby available on macOS.
$ sudo gem install cocoapods
You specify the dependencies for your project in a simple text file Podfile
.
platform :ios, '9.0'
target 'MyApp' do
pod 'AFNetworking', '~> 3.0'
pod 'FBSDKCoreKit', '~> 4.9'
end
CocoaPods recursively resolves dependencies between libraries, fetches source code for all dependencies, and creates and maintains an Xcode workspace to build your project.
CocoaPods supports any source management system. (Currently supported are git, svn, mercurial, bazaar, and various types of archives downloaded over HTTP.)
When you use CocoaPods, it makes several changes to your Xcode project and binds the result, along with a special Pods
project, into an Xcode workspace.
Whether or not you check in your Pods
folder is up to you, as workflows vary from project to project. It’s recommended that you keep the Pods directory under source control, and don’t add it to your .gitignore. But ultimately this decision is up to you:
CocoaPods itself does not require the use of a workspace. If you prefer to use sub-projects, you can do so by running pod install --no-integrate
, which will leave integration into your project up to you as you see fit.
Carthage is intended to be the simplest way to add frameworks to your Cocoa application.
brew install carthage
Once you have Carthage installed, you can begin adding frameworks to your project. Note that Carthage only supports dynamic frameworks, which are only available on iOS 8 or later (or any version of OS X).
List the desired dependencies in the Cartfile
, for example:
github "Alamofire/Alamofire" ~> 5.5
github "Alamofire/AlamofireImage" ~> 3.4
Carthage builds your dependencies and provides you with binary frameworks, but you retain full control over your project structure and setup. Carthage does not automatically modify your project files or your build settings.
You must drag the built .xcframework bundles from Carthage/Build into the “Frameworks and Libraries” section of your application’s Xcode project.
Benefits of using Carthage: