Home · iOS & Swift Tutorials

Carthage Tutorial: Getting Started

In this Carthage tutorial, you’ll learn what Carthage is, how to install it and how to use it to declare, install and integrate your dependencies.

4.7/5 6 Ratings

Version

  • Swift 5, iOS 13, Xcode 11
Update note: Felipe Laso Marsetti updated this tutorial for iOS 13, Xcode 11 and Swift 5. James Frost wrote the original.

One of the great things about iOS development is the wide range of third-party libraries available to use. If you’ve tried some of these libraries, you know the value of using someone else’s code instead of reinventing the wheel.

With so many libraries available, managing dependencies can get tricky. That’s where dependency managers come in.

Carthage is a simple dependency manager for macOS and iOS, created by a group of developers from GitHub.

Not only was Carthage the first dependency manager to work with Swift, but it’s also written in Swift! It exclusively uses dynamic frameworks, rather than static libraries.

In this Carthage tutorial, you’ll:

  • Learn why and when to use a dependency manager and what makes Carthage different from other tools.
  • Install Carthage.
  • Declare dependencies, installing and integrating them within a project.
  • Upgrade your dependencies to different versions.

You’ll do this by building an app that provides definitions for search terms using the DuckDuckGo API.

Getting Started

Use the Download Materials button at the top or bottom of this tutorial to download the starter project.

It includes the basic skeleton of DuckDuckDefine, a simple tool to look up definitions and images using the DuckDuckGo API. There’s one problem: It doesn’t perform any searches yet!

Open DuckDuckDefine.xcodeproj in Xcode and have a quick look. Note the two view controllers: SearchViewController provides a search bar for the user to perform a search and DefinitionViewController displays the definition of a search term.

Looking at the files in the Project Navigator

The brains of the operation are in DuckDuckGo.swift – well, they will be by the time you finish! At the moment, performSearch(for:completion:) is a lazy, good-for-nothing block of code.

To perform a search and display the results, you’ll need to do two things:

  • Make a query using the DuckDuckGo API.
  • Show an image for the retrieved word.

There are many open source libraries that can help with these two tasks. Alamofire is a great Swift library that simplifies making web requests. AlamofireImage makes dealing with images in Swift more pleasant.

And guess what? You’ll use Carthage to add both of these dependencies to your project.

Advantages of Dependency Management

To add Alamofire and AlamofireImage to your project, you could visit their respective GitHub pages, download zip files containing their source code and drop them into your project. So why bother with a tool like Carthage?

Dependency managers perform some handy functions, including:

  • Simplifying and standardizing 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.
  • Making it easier to update third-party libraries in the future. Imagine having to visit each dependency’s GitHub page, download the source, and place it into your project every time there’s an update. Why would you do that to yourself?
  • Selecting appropriate and compatible versions of each dependency you use. For instance, manually adding dependencies can get tricky when they depend on one another or share another dependency.

How dependency management works as a hierarchy

Most dependency managers construct a dependency graph of your project’s dependencies and their sub-dependencies, then determine the best version of each to use.

You could do the same manually, but at what cost? Your sanity? Dependency managers are easier and less error-prone.

Comparing Dependency Managers

One of the most popular dependency managers is CocoaPods, which streamlines integrating libraries into your project. It’s widely used in the iOS community.

Apple offers its own dependency manager called Swift Package Manager to share and distribute packages in Swift 3.0 and above.

While CocoaPods and Swift Package Manager are great tools, Carthage offers a simpler and more hands-on alternative. So which one is right for you? In the next section, you’ll compare Carthage with some of the other popular tools.

Carthage Versus CocoaPods

How is Carthage different from CocoaPods, and why would you use anything besides the most popular dependency manager for iOS?

While CocoaPods is easy to use, it is not simple. The philosophy behind Carthage is that a dependency manager should be simple.

CocoaPods adds complexity to the app development and the library distribution processes:

  • Libraries must create, update and host Podspec files. App developers must write their own if none exist for a library they wish to use.
  • When adding pods to a project, CocoaPods creates a new Xcode project with a target for each individual pod, plus a workspace to contain them. You have to use the workspace and trust that the CocoaPods project works. You also have to maintain those extra build settings.
  • CocoaPods uses a centralized Podspecs repository. This can disappear or become inaccessible, causing problems.

Carthage aims to provide a simpler tool that’s more flexible and easier to understand and maintain.

Here’s how Carthage achieves this:

  • It doesn’t change your Xcode project or force you to use a workspace.
  • You don’t need Podspecs or a centralized repository where library authors submit their pods. If you can build your project as a framework, you can use it with Carthage, which leverages existing information straight from Git and Xcode.
  • Carthage doesn’t do anything magically; you’re always in control. You add dependencies to your Xcode project and Carthage fetches and builds them.
Note: Carthage uses dynamic frameworks to achieve its simplicity. This means your project must support iOS 8 or later.

Carthage Versus Swift Package Manager

How about the differences between Carthage and Swift Package Manager?

The main focus of the Swift Package Manager is to share Swift code in a developer-friendly way. Carthage’s focus is to share dynamic frameworks. Dynamic frameworks are a superset of Swift packages.

A package is a collection of Swift source files plus a manifest file. The manifest file defines the package’s name and its content. A package contains Swift code, Objective-C code, non-code assets like images or any combinations of the three.

SPM has a lot going for it, especially since Xcode 11 has built-in support for SPM packages. However, at the time of writing, SPM has a couple of limitations that prevent a lot of libraries from supporting it. For instance, it doesn’t support sharing already built binaries, only the source code of your packages. If you want to have a closed-source library, you can’t use SPM. Additionally, SPM supports adding source code to frameworks, but not resources like images or other data files.

This means that a lot of libraries you might use won’t get SPM support anytime soon. For now, you’ll still have to rely on Carthage or CocoaPods for those libraries.

Installing Carthage

Now that you’ve gained some background knowledge, it’s time to learn how ruthlessly simple Carthage is!

At Carthage’s core is a command-line tool that assists with fetching and building dependencies.

There are two ways to install this tool:

  • Download and run a .pkg installer for the latest release.
  • Use the Homebrew package manager.

Just as Carthage helps install packages for Cocoa development, Homebrew helps install useful Unix tools for macOS.

For the purposes of this Carthage tutorial, you’ll use the .pkg installer.

Download the latest release of Carthage from GitHub. Then, under Assets, select Carthage.pkg.

Double-click Carthage.pkg to run the installer. Click Continue, select a location to install to, click Continue again and finally click Install.

Note: When you attempt to run the installer, you may see a message stating: “Carthage.pkg can’t be opened because it is from an unidentified developer.” If so, Control-click the installer and choose Open from the context menu.

And you’re done! To check that Carthage installed correctly, open Terminal and run the following command:

carthage version

This shows you the Carthage version you installed.

Next, you need to tell Carthage which libraries to install using a Cartfile.

Creating Your First Cartfile

A Cartfile is a simple text file that describes your project’s dependencies to Carthage, so it can determine what to install. Each line in a Cartfile states where to fetch a dependency, the dependency’s name, and optionally, which version to use. A Cartfile is the equivalent of a CocoaPods Podfile.

To create your first one, go to Terminal, then navigate to the root directory of your project – the directory that contains your .xcodeproj file – using the cd command:

cd ~/Path/To/Starter/Project

Create an empty Cartfile with the touch command:

touch Cartfile

Then open the file in Xcode for editing:

open -a Xcode Cartfile

If you’re familiar with another text editor, like Vim, feel free to use that instead. Don’t, however, use TextEdit to edit the file. With TextEdit, it’s too easy to accidentally use “smart quotes” instead of straight quotes, which confuse Carthage.

Add the following lines to the Cartfile and save it:

github "Alamofire/Alamofire" == 4.9.0
github "Alamofire/AlamofireImage" ~> 3.4

These two lines tell Carthage that your project requires Alamofire version 4.9.0 and the latest version of AlamofireImage that’s compatible with version 3.4.

The Cartfile Format

You write Cartfiles in a subset of OGDL: Ordered Graph Data Language. This sounds fancy, but it’s quite simple. There are two key pieces of information on each line of a Cartfile:

  • Dependency origin: This tells Carthage where to fetch a dependency. Carthage supports two types of origins:
    • github for GitHub-hosted projects (the clue’s in the name!). You specify a GitHub project in the Username/ProjectName format, as you did with the Cartfile above.
    • git for generic Git repositories hosted elsewhere. You use the git keyword followed by the path to the git repository, whether that’s a remote URL using git://, http://, or ssh:// or a local path to a Git repository on your development machine.
  • Dependency version: Here, you tell Carthage which version of a dependency you want to use. There are several options at your disposal, depending on how specific you want to be:
    • == 1.0: Indicates “Use exactly version 1.0.”
    • >= 1.0: Means “Use version 1.0 or higher.”
    • ~> 1.0: Translates to “Use any version that’s compatible with 1.0,” meaning any version up to the next major release.
    • Branch name / tag name / commit name means “Use this specific git branch / tag / commit”. For example, you could specify master, or a commit hash like 5c8a74a.

Here are some examples:

If you specify ~> 1.7.5, Carthage considers any version from 1.7.5 up to, but not including 2.0, compatible.

Likewise, if you specify ~> 2.0, Carthage uses version 2.0 or any later versions, but not 3.0 or above.

Carthage uses semantic versioning to determine compatibility.

If you don’t specify a version, Carthage will use the latest version that’s compatible with your other dependencies. You can see examples of each of these options in Carthage’s README file.

Building Dependencies

Now that you have a Cartfile, it’s time to put it to use and install some dependencies!

Note: This Carthage tutorial uses Swift 5. At the time of writing, Swift 5 is only available in Xcode 11. Ensure you’ve configured your command line tools to use Xcode 11 by running the following command from Terminal:
sudo xcode-select -s <path to Xcode 11>/Xcode.app/Contents/Developer 

Be sure to replace path to Xcode 11 with your machine’s specific path to Xcode 11.

Close your Cartfile in Xcode and head back to Terminal. Run the following command:

carthage update --platform iOS

This instructs Carthage to clone the Git repositories from the Cartfile, then to build each dependency into a framework. You’ll see output that shows the results, like this:

*** Cloning AlamofireImage
*** Cloning Alamofire
*** Checking out Alamofire at "4.9.0"
*** Checking out AlamofireImage at "3.6.0"
*** xcodebuild output can be found in /var/folders/bj/3hftn5nn0qlfrs2tqrydgjc80000gn/T/carthage-xcodebuild.7MbtQO.log
*** Building scheme "Alamofire iOS" in Alamofire.xcworkspace
*** Building scheme "AlamofireImage iOS" in AlamofireImage.xcworkspace

--platform iOS ensures that Carthage only builds frameworks for iOS. If you don’t specify a platform, Carthage will build frameworks for all platforms — often both Mac and iOS — supported by the library.

If you’d like to take a look at further options, run carthage help update.

By default, Carthage performs its checkouts and builds in a new directory named Carthage, which you’ll find in the same location as your Cartfile. Open this directory now by running:

open Carthage

You’ll see a Finder window appear that contains two directories: Build and Checkouts. Take a moment to see what Carthage created for you.

Generated Carthage/ folder contents

Building Artifacts

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.

Carthage is a little different. It checks the code for your dependencies and builds the result into binary frameworks. It’s then up to you to integrate the frameworks into your project.

This sounds like extra work, but it’s beneficial. It only takes a few steps, and you’re more aware of the changes to your project as a result.

When you run carthage update, Carthage creates a couple of files and directories for you:

Carthage update folder structure and hierarchy

  • Cartfile.resolved: This file serves as a companion to the Cartfile. It defines exactly which versions of your dependencies Carthage selected for installation. It’s strongly recommended to commit this file to your version control repository. Its presence ensures that other developers can get started quickly by using the exact same dependency versions.
  • Carthage directory, containing two subdirectories:
    • Build: This contains the built framework for each dependency. You can integrate these into your project, and you’ll do so shortly. Carthage either builds each framework from source or downloads it from the project’s Releases page on GitHub.
    • Checkouts: This is where Carthage checks out the source code for each dependency that’s ready to build into frameworks. Carthage maintains its own internal cache of dependency repositories, so it doesn’t have to clone the same source multiple times for different projects.

Avoiding Problems With Your Artifacts

Whether you commit the Build and Checkouts directories to your version control repository is up to you. It’s not required, but doing so means that anybody who clones your repository will have the binaries and source for each dependency available.

Having this backup can be a useful insurance policy if GitHub is unavailable or a source repository is removed.

Don’t change any code inside the Checkouts folder because a future carthage update or carthage checkout command may overwrite its contents at any time. Your hard work would be gone in the twinkling of an eye.

If you must modify your dependencies, run carthage update using the --use-submodules option.

With this option, Carthage adds each dependency in the Checkouts folder to your Git repository as a submodule, meaning you can change the dependencies’ source, and commit and push those changes elsewhere, without fear of an overwrite.

Note: If other developers use your project and you haven’t committed the built frameworks with your code, they’ll need to run carthage bootstrap after checking out your project.

The bootstrap command downloads and builds the exact versions of your dependencies specified in Cartfile.resolved. carthage update would update the project to use the newest compatible versions of each dependency, which may not be desirable.

Now, how about actually using these build artifacts you worked so hard to create? You’ll do this in the next section.

Adding Frameworks to Your Project

Back in Xcode, click the DuckDuckDefine project in Project navigator. Select the DuckDuckDefine target. Choose the General tab at the top then scroll to the Frameworks, Libraries, and Embedded Content section at the bottom.

In the Carthage Finder window, navigate to Build/iOS. Drag both Alamofire.framework and AlamofireImage.framework into the Linked Frameworks and Libraries section in Xcode:

Adding AlamoFire frameworks to Xcode project

This tells Xcode to link your app to these frameworks, allowing you to make use of them in your code.

Next, switch to Build Phases. Click the + icon in the top-left of the editor and select New Run Script Phase. Add the following command to the block of code under Run Script:

/usr/local/bin/carthage copy-frameworks

Click the + icon under Input Files and add an entry for each framework:

$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
$(SRCROOT)/Carthage/Build/iOS/AlamofireImage.framework

The result looks like this:

Adding a run script phase for the Carthage frameworks

Strictly speaking, this build phase isn’t required for your project to run. However, it’s a slick workaround for an App Store submission bug where apps with frameworks that contain binary images for the iOS simulator are automatically rejected.

The carthage copy-frameworks command strips out these extra architectures. WOOt!

Although there won’t be anything new to see yet, build and run to ensure everything’s working as expected. When the app launches, you’ll see the search view controller.

An empty dictionary app

OK, great. Everything looks good. Next, you’ll work on upgrading dependencies.

Upgrading Frameworks

I have a confession to make.

Remember when you created your Cartfile earlier and I told you which versions of Alamofire and AlamofireImage to install? Well… I gave you bad information. I told you to use an old version of Alamofire.

Don’t be mad though! I did it with the best of intentions. Look at this as an opportunity to learn how to upgrade a dependency.

Open your Cartfile again. From your project’s directory in Terminal, run:

open -a Xcode Cartfile

Change the Alamofire line to:

github "Alamofire/Alamofire" ~> 4.9.0

As you saw earlier, this code instructs Carthage to use any version of Alamofire that’s compatible with 4.9.0. This includes any version up to but not including a future 5.0 version.

When adding dependencies with Carthage, you want to consider compatibility and limit the version you target. That way, you know the exact state of its API and functionality.

For example, version 5.0 of a dependency might include app-breaking API changes. You wouldn’t want to automatically upgrade to that version if you built your project against 4.9.0.

Save and close the Cartfile and return to the terminal. Perform another update:

carthage update --platform iOS

This tells Carthage to look for newer versions of each of your dependencies. It will then check them out and build them, if necessary. In this case, it fetches the latest version of Alamofire.

Because your project already contains a reference to the built .framework for Alamofire, and Carthage rebuilds the new version in the same location on disk, you can sit back and let Carthage do the work. Your project will automatically use the latest version of Alamofire!

Duck, Duck… GO!

Now that you’ve integrated Alamofire and AlamofireImage into the project, you can put them to use by performing some web searches.

In Xcode, open DuckDuckGo.swift. At the top of the file, add the import below:

import Alamofire

Next, replace the contents of the existing performSearch(for:completion:) with this:

// 1
let parameters: Parameters = [
  "q": term,
  "format": "json",
  "pretty": 1,
  "no_html": 1,
  "skip_disambig": 1
]

// 2
Alamofire.request(
  "https://api.duckduckgo.com",
  method: .get,
  parameters: parameters)
  .responseData { response in
    // 3
    guard 
      response.result.isSuccess, 
      let jsonData = response.result.value 
      else {
        completion(nil)
        return
    }
    
    // 4
    let decoder = JSONDecoder()
    
    guard 
      let definition = try? decoder.decode(Definition.self, from: jsonData),
      definition.resultType == .article 
      else {
        completion(nil)
        return
    }
    
    completion(definition)
}

Here’s what this does:

  1. First, you build the parameters to send to DuckDuckGo. The most important two here are q, the search term itself, and format, which tells the web service to respond with JSON.
  2. Then you perform the request using Alamofire. This call makes a GET request to https://api.duckduckgo.com, using the parameter dictionary created above.
  3. Once the response comes back, check if the request failed and optionally bind the JSON response object to ensure it has a value. If something is wrong, exit early.
  4. Next, use JSONDecoder to deserialize the Definition, which conforms to Codable. The DuckDuckGo API can return a range of different result types, but the one covered here is Article, which provides a simple definition of the search term. You filter for article then pass the retrieved definition to the completion handler.
Note: If you’re wondering why the skip_disambig parameter exists, it’s to tell DuckDuckGo not to return ‘disambiguation’ results.

Disambiguation results are like this Christopher Evans page on Wikipedia that clarifies whether you meant Chris Evans the movie actor, Chris Evans the British TV personality or Chris Evans the train robber.

skip_disambig means the API will pick the most likely result and return it.

Build and run. Once the app starts, enter “Duck” in the search bar. You’ll now see a definition on the next screen.

Performing a search for Duck in Duck Duck Go

There’s something missing, however: a picture! It’s one thing to read what a duck is, but who reads anymore? Pictures are worth — okay, I’ll spare you the cliché, you know what I mean.

Anyway, who doesn’t like looking at pictures of ducks? Kittens are so last season, right?

Open DefinitionViewController.swift and add a new import below the existing UIKit import at the top:

import AlamofireImage

Then, add the following code below viewDidLoad:

override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
    
  if let imageURL = definition.imageURL {
    imageView.af_setImage(withURL: imageURL) { _ in
      self.activityIndicatorView.stopAnimating()
    }
  }
}

af_setImage is an extension of UIImageView that AlamofireImage provides. You call it to retrieve the image found in the definition imageURL. Once retrieved, the activity indicator’s animation stops.

Build and run, then perform your search again.

Downloading an image for a duck

Quack quack!

Where to Go From Here?

You can download the completed version of the project using the Download Materials button at the top or bottom of this tutorial.

Congratulations, you’ve learned about the philosophy behind dependency management and Carthage itself. You gained experience using Carthage to add dependencies to a project and used those dependencies to make a useful app!

You also know how to update your dependencies for future releases.

If you want to learn more about Carthage, your first stop should be the Carthage README and the documentation on Build Artifacts.

Finally, if you’d like to learn more about Swift Package Manager, be sure to check out our tutorial Swift Package Manager for iOS or our video course Moving from CocoaPods to Swift Package Manager. About CocoaPods, you can check out our tutorial on How to Use CocoaPods With Swift. It also contains a great section on Semantic Versioning, which you saw in use in Cartfiles.

I hope you got a lot out of this Carthage tutorial. If you have any questions or comments, please join the forum discussion below!

Average Rating

4.7/5

Add a rating for this content

6 ratings

More like this

Contributors

Comments