Swift Package Manager vs CocoaPods vs Carthage for All Platforms

With the advent of Swift 3.0, Apple has released an official package manager called Swift Package Manager (AKA SPM or SwiftPM), that helps us manage dependencies.

But there are two other package managers out there: CocoaPods and Carthage.

In this tutorial, you will learn about the pros and cons of each package manager, and how to use them to integrate frameworks into your codebase.

You will be using a simple to-do app called EasyToDo, to learn about each integration. This app has a backend that allows users to synchronize all their to-do lists.

You’ll use Xcode 10 and Swift 4.2 in this tutorial, but CocoaPods and Carthage also support Objective-C.

Why should I use it?

You might be asking yourself, “Can’t I just copy the source code inside my app project and use that?” Yes, you can, but what will you do if a critical update comes up? You will need to manually follow the repository release updates and keep checking for new versions. That doesn’t sound like fun, does it? Well, you’re in luck! By using a package manager, you can easily make sure that your code is always up to date.

What is a package manager?

Great question! A package manager is a tool that automates the process of installing, upgrading, configuring, and removing a software, or in this case, inside our app.

But what does that really mean? Let's suppose you need to handle network requests inside your app, but you don't want to reinvent the wheel. You just want to use a robust, reliable, well crafted, and well-tested framework.

With a package manager, you can easily manage dependencies inside your software, like Alamofire to help you better handle the network requests inside your to do app.

CocoaPods

CocoaPods is a centralized dependency manager for Swift and Objective-C Cocoa projects. It is open-source and was built with Ruby by many volunteers and the open-source community.

What does centralized mean? Well, CocoaPods is based on a main repository called Specs that hosts all the framework specifications. In order to make it available to others, package developers have to push new versions to this repository by using the pod command line.

CocoaPods has a wonderful public search feature available on its official website so you don't have to scan the web to find the right dependencies.

CocoaPods Search Feature
Search feature on CocoaPods website

Supported Platforms

All Apple platforms, iOS, tvOS, watchOS, and macOS. CocoaPods command line tool only works on a Mac.

How To Use It

First, you need to install it on your Mac, open Terminal and type

sudo gem install cocoapods

Then, to integrate a dependency using CocoaPods, you have to create a Podfile in your project root folder, like the following one:

# 1
source 'https://github.com/CocoaPods/Specs.git'
# 2
platform :ios, '9.0'
# 3
use_frameworks!

# 4
target 'EasyToDo' do
  # 5
  pod 'Alamofire'
end

Now open the Terminal App on your Mac and type

pod install

CocoaPods Install

You have successfully integrated your dependency! 🎉

Let's see what you’ve done:

  1. You specified the specs source repository. This allows you to add an external repository, like a private one.
  2. You specified that your app is an iOS app that works on iOS 9 or later. You can set whatever version you want.
  3. You told CocoaPods to integrate your dependencies as Dynamic Frameworks instead of Static Libraries. From Xcode 9, Swift 4, and CocoaPods 1.5.0, you can use Static Libraries for Swift as well. We’ll use Dynamic Frameworks in this tutorial.
  4. You added your app’s name as the target. You can specify different dependencies for every target in your project, for example, a macOS app.
  5. And finally, this is your dependency. In this case, you’re just adding Alamofire. On this line, you can also specify what version of pod you’d like to use. For example, major, minor or patch version.

After you run pod install on the Terminal, a Podfile.lock file will be created to let other developers, or even you, add the same version of the same dependencies to your app. If your project is under version control, usually this file is pushed to the repository.

A folder called Pods was also created. Inside this folder, you will find everything is related to the CocoaPods integration. If your project is under version control, this file is usually pushed to the repository too, but this is optional.
From now on, you must use the .xcworkspace file, instead of the .xcodeproj file to open your app's source code. This is needed in order to integrate your original app project and the CocoaPods project inside a single workspace.

Go to your Swift file and type

import Alamofire

You can now use Alamofire inside your file!

Advantages

  1. You can search for a dependency on the official CocoaPods website.
  2. Supports both Dynamic Frameworks and Static Libraries (since version 1.5.0).
  3. Automatically manage a dependency’s dependencies. If a dependency relies on another dependency, CocoaPods will handle it for you.
  4. Anyone can easily tell what dependencies your app is using.
  5. It's easy to check if a new version of a dependency is available by using the command pod outdated.
  6. If the dependency supports it, you can try the dependency before integrating it into your project by using the command pod try Alamofire.
  7. Has an official Mac app to easily manage app dependencies.
  8. Almost every framework supports CocoaPods.

Disadvantages

  1. You will have to wait a long time the first time you install your dependencies, even if it’s just one, as CocoaPods will have to download the main Specs repository on your Mac. This will also happen on every pod update command when you want to update your dependencies.
  2. Your main project will be modified to be able to use all of your dependencies. It's also true that you can remove the CocoaPods integration by using the command pod deintegrate.
  3. Every time you build your project, all your dependencies will also be built, which leads to slower build times.

List Of Useful Commands

  • pod init - Create the initial Podfile file for you.
  • pod install - Install the dependencies based on Podfile.lock file.
  • pod update - Update the dependencies based on Podfile file.
  • pod update <dependency name> - Update a dependency based on Podfile file.
  • pod repo update - Update the local Specs repository, that is no more update on pod install, but only on pod update.
  • pod outdated - Show the outdated dependencies.
  • pod try <dependency name> - Lets you try a dependency.
  • pod deintegrate - Removes all the CocoaPods integration in the project.

Visit Codementor Events

Carthage

Carthage is a decentralized dependency manager for Swift and Objective-C Cocoa projects. It is open-source and built with Swift by the open-source community.

What does decentralized mean? Unlike CocoaPods, you don't have a main Specs repository. This reduces maintenance work and avoids any central points of failure, but project discovery is more difficult. For example, this means that checking for outdated dependencies means checking every dependency repository instead of a single centralized one.

Supported Platforms

All Apple platforms, iOS, tvOS, watchOS, and macOS. Carthage command line tool works only on a Mac.

How To Use It

First, you need to install it on your Mac. There are two ways to do it:

  1. Download the installer package here.
  2. Open Terminal and type brew install carthage. You can find out how to install it here.

Then, to integrate a dependency using Carthage, you have to create a Cartfile in your project root folder, like the following one:

github "Alamofire/Alamofire"

Now open the Terminal App on your Mac and type

carthage update

Carthage Update

You have successfully integrated your dependency! 🎉

Let's see what you have done:

  1. You have specified that your dependency is on GitHub and is on the Alamofire/Alamofire repository.

That’s all! But a lot of things happened under the hood.

Carthage created a Cartfile.resolved file to let other developers, or you, add the same version of the same dependencies to your app. If your project is under version control, you would normally push this file to the repository.

It created a Carthage folder in your project root folder. Inside this folder, it checked out every dependency declared in your Cartfile. If your project is under version control, you would normally push this file to the repository as well.

It also created a Build folder inside it, where you will find a folder for every platform that your dependencies support. For example, for Alamofire, a framework that supports all Apple platforms, you will find iOS, Mac, tvOS, and watchOS folders.

If you want to only build dependencies for a specified platform, use carthage update --platform iOS command.

Inside every platform folder, you will find all the frameworks that support that platform (you will also find other files and a .dSYM file, which is used for desymbolising crash logs).

There are a few other steps we need to do (from official repository documentation):

  1. Drag the built .framework binaries from Carthage/Build/<platform> into your application’s Xcode project.
  2. On your application targets’ Build Phases settings tab, click the + icon and choose New Run Script Phase. Create a Run Script in which you specify your shell (ex: /bin/sh), add the following contents to the script area below the shell:
/usr/local/bin/carthage copy-frameworks
  1. Add the paths to the frameworks you want to use under “Input Files":
$(SRCROOT)/Carthage/Build/iOS/Alamofire.framework
  1. Add the paths to the copied frameworks to the “Output Files”:
$(BUILT_PRODUCTS_DIR)/$(FRAMEWORKS_FOLDER_PATH)/Alamofire.framework

Go to your Swift file and type

import Alamofire

You can now use Alamofire inside your file!

Advantages

  1. Supports both Dynamic Frameworks and Static Libraries (since version 0.30.0).
  2. Automatically manage a dependency’s dependencies. If a dependency relies on another dependency, Carthage will handle it for you.
  3. Anyone inside the project will easily know what dependencies your app is using.
  4. It's easy to check if a new version of a dependency is available by using the carthage outdated command.
  5. Your project builds faster in comparison to CocoaPods as Carthage only builds the frameworks once (when you call the carthage update or carthage bootstrap command).
  6. Your project remains untouched since you will only add frameworks and a new build phase.

Disadvantages

  1. (Too) many steps, which make it easy to miss adding a new dependency to the Carthage build phase.
  2. Not every framework supports Carthage.

List Of Useful Commands

  • carthage bootstrap - Fetch and build all the dependencies based on Cartfile.resolved file.
  • carthage bootstrap --platform <platform>,<platform> - Fetch and build all the dependencies for specific platforms based on Cartfile.resolved.
  • carthage update - Fetch and build all the dependencies based on Cartfile file.
  • carthage update --platform <platform>,<platform> - Fetch and build all the dependencies for specific platforms based on Cartfile file.
  • carthage build - Build all the dependencies without fetching them.
  • carthage outdate - Show the outdated dependencies.

Swift Package Manager

Also known as SwiftPM or SPM, it's been included in Swift since version 3.0. From the official repository:

The Swift Package Manager is a tool for managing distribution of source code, aimed at making it easy to share your code and reuse others’ code.

Swift Package Manager is also used to create backend apps by using frameworks like Vapor, Kitura or Perfect.

Your app can only use corelibs or other dependencies based on top of them. Currently, there are:

  • Foundation - Core utils, it contains String, Array and all the others
  • Dispatch - Concurrency helper also known as Grand Central Dispatch or GCD
  • XCTest - Unit Tests support

Thanks to that, we will create a backend App instead of an iOS App.

Supported Platforms

Currently supports only macOS and Linux 14.04, 16.04, and 18.04.

How To Use It

  • For macOS, you just need Xcode 10;
  • For Linux, you have to install it manually or use swiftenv, which will even help you manage multiple Swift versions.

To be able to build, test, and use your project, you have to use a specific folder structure, like Sources for the source files and Tests for tests files. Inside the Sources folder, you have to create a main.swift file. This will be the main App source code.

Now let's create our app package description. Create a Package.swift file inside your project root folder.
Inside it, we have to define all the characteristics of our app:

// 1
import PackageDescription

// 2
let package = Package(
    // 3
    name: "EasyToDo",
    // 4
    dependencies: [
        // 5
        .package(url: "https://github.com/apple/swift-nio")
    ],
    // 6
    targets: [
        // 7
        .target(name: "EasyToDo")
    ]
)

Now open Terminal and type

swift build

If everything went ok, you have successfully created your App! 🎉

See what you have done:

  1. Since this a normal Swift source file, we had to import a module.
  2. You created a Package, this is where you defined all your App characteristics.
  3. Here, you defined your app name.
  4. This is the dependencies array where you defined all the app dependencies.
  5. You declared swift-nio as a dependency because Alamofire doesn't support Linux. This can even be a local repository.
  6. This is the targets array, you can define more than one target and divide your app in multiple targets, but for now, we only declared one.
  7. This is the real app target.

After running swift build on the Terminal, a Package.resolved file will be created to let other developers, or even you, add the same version of the same dependencies to your app. If your project is under version control, you would usually push this file to the repository.

A .build folder will be created for you, where you will find everything related to Swift Package Manager. You will find all the dependency repositories and your executable app.

To execute your app, you can type swift run.

If you are on a Mac, you can even automatically generate the Xcode project with swift package generate-xcodeproj. From now on, you can use Xcode to build and maintain your app.

Now we can use our dependency inside our app. Open main.swift source file and type

import NIO

You can now use SwiftNIO inside your file!

Advantages

  1. It’s the new standard build by Apple to create Swift apps.
  2. Automatically manage a dependency’s dependencies. If a dependency relies on another dependency, Swift Package Manager will handle it for you.
  3. Anyone inside the project will easily know what dependencies your app is using.
  4. Works on Linux.

Disadvantages

  1. Currently doesn't support all the platforms like iOS, watchOS, or tvOS.
  2. You have to follow a specific folder structure.
  3. Some features on the Foundation corelib are unimplemented. You can find the current status here.
  4. Creating Unit Tests on Linux is not easy as on macOS. You also have to do additional turnaround.

List Of Useful Commands

  • swift package init - Initialize and create a new package.
  • swift package resolve - Resolve all the dependencies based on Package.resolved file.
  • swift package update - Update all the dependencies based on Package.swift file.
  • swift build - Build all the source files.
  • swift package generate-xcodeproj - Generate a xcodeproj file so you can use Xcode to develop.
  • swift package generate-xcodeproj --watch - Generate a xcodeproj file so you can use Xcode to develop and watch for source file folders changes.
  • swift package clean - Delete all the build artifacts.

Swift help Swift developers.png

What Package Manager Should I Use?

The below flowchart has only some simple questions to help you choose the best package manager that better fits your needs.

What Package Manager Should I Use?

Summary

We learned how the package managers available for Apple platforms (even Linux!) differ, how they work, and how to use them. Now it’s up to you to choose the best package manager for your project. If you need more help, you can always hire a Swift developer to help you. That said, here are some useful lonks to help you create your own framework that supports all the package managers:

If you have any questions or comments, please leave a comment below!

Free Swift projects.png

Last updated on Aug 04, 2023