Getting started - AWS SDK for Swift

Getting started

In this guide, we’ll look at how use the Swift Package Manager — part of the standard Swift toolchain — to create and build a small project that uses the SDK for Swift to access Amazon Cognito Identity.

Prerelease documentation

This is prerelease documentation for an SDK in preview release. It may be incomplete and is subject to change.

In addition, versions of the SDK prior to version 1.0.0 may have flaws, and no guarantee is made about the API’s stability. Changes can and will occur that break compatibility during the prerelease stage. These releases are not intended for use in production code!

Creating a project using the AWS SDK for Swift

In this article, we’ll create a small program that uses the default AWS settings to authenticate with AWS, then find the ID of an Amazon Cognito Identity identity pool, creating it if it doesn’t already exist.

Our goals for this project:

  • Create a project using Swift Package Manager

  • Add AWS SDK for Swift to the project

  • Configure the project’s Package.swift file to describe the project and its dependencies.

  • Add code that uses Amazon Cognito Identity to obtain the ID of an identity pool by name, creating the pool if needed.

Before we begin, make sure you’ve prepared your development environment as described in Setting up. To make sure you’re set up properly, use the following command to ensure that Swift is available and to ensure that the version installed is at least 5.4.

$ swift --version

If you’re using Swift on a Mac, you should see output that looks like this (with possibly different version and build numbers).

swift-driver version: 1.26 Apple Swift version 5.5 (swiftlang-1300.0.20.104 clang-1300.0.21.1)
Target: x86_64-apple-macosx11.0

On Linux, the output should look something like the following:

Swift version 5.4.2 (swift-5.4.2-RELEASE)
Target: x86_64-unknown-linux-gnu

If Swift is not installed, or is older than version 5.4, follow the instructions in Setting up to install or reinstall the tools.

Get this example on GitHub

You can fork or download this example from the AWS SDK for Swift code examples repository.

Creating the project

With the tools installed, open a terminal session using your favorite terminal application (such as Terminal, iTerm, or the integrated terminal in your editor). At the terminal prompt, go to a the directory in which you want to create the project, then enter the following series of commands to create an empty Swift project for a standard executable program.

$ mkdir cognito-identity-demo $ cd cognito-identity-demo $ swift package init --type executable

This creates the directory for the example’s project, moves into that directory, then initializes that directory with the Swift Package Manager. The result is the basic file system structure for a simple executable program.

cognito-identity-demo/
├── Package.swift
├── README.md
├── Sources/
│  └── cognito-identity-demo/
│     └── main.swift
└── Tests/
   └── CognitoIdentityDemoTests/
      └── CognitoIdentityDemoTests.swift

Now that the project has been created, open it up in your preferred text editor or IDE. On macOS, you can open the project in Xcode with:

$ xed .

As another example, you can open the project in Visual Studio Code with:

$ code .

Configuring the package

Once the project is open in your editor, open the file Package.swift. This is a Swift file that defines an SPM Package object that describes the project and its dependencies, its build rules, and other information needed to create the documentation set.

At the very top of this file is a comment indicating that the file defines an SPM package, and which version of the Swift tool is the minimum required. This needs to be at least version 5.4 for compatibility with AWS SDK for Swift.

// swift-tools-version:5.4

Now we can update the Package.swift file to properly describe our project and its dependencies. The package’s description is provided in an object of type of type Package named package.

import PackageDescription let package = Package( ... )

Define general package attributes

Next, we update the contents of package to set its attributes describing the package, starting with the project’s name and the platforms and versions of those SDKs that the project is compatible with.

name: "cognito-identity-demo", platforms: [ .macOS(.v10_15), .iOS(.v13) ],

Specify the package’s dependencies

The dependencies attribute is an array of packages that the project needs in order to build. In our case, we need to import the AWS SDK for Swift — found in a package named "AWSSwiftSDK" — from the Github repository on which it lives, specifying the minimum version of the SDK that’s needed. We also specify that we wish to retrieve version 0.1.0 or later.

dependencies: [ .package( name: "AWSSwiftSDK", url: "https://github.com/awslabs/aws-sdk-swift", from: "0.1.0" ), ],

List the package’s build targets

Next comes the targets array, which lists the project’s build targets: a library containing objects that handle operations that interact with the SDK for Swift, the project’s tests, and the final executable program.

The main program is specified using the Package.executableTarget() method, and specifies the target’s name and what its dependencies are. This project depends on its CognitoIdentityDemo target and the AWS SDK for Swift module AWSCognitoIdentity.

.executableTarget( name: "cognito-identity-demo", dependencies: [ "CognitoIdentityDemo", .product(name: "AWSCognitoIdentity", package: "AWSSwiftSDK"), ] ),

The CognitoIdentityDemo target is defined next, using the Package.target() method. This target has only one dependency: the SDK for Swift module AWSCognitoIdentity. This is the class that performs the actual interactions with the SDK.

.target( name: "CognitoIdentityDemo", dependencies: [ .product(name: "AWSCognitoIdentity", package: "AWSSwiftSDK"), ] ),

The last target, CognitoIdentityDemoTests, defines the executable program used to run the program’s automated tests.

.testTarget( name: "CognitoIdentityDemoTests", dependencies: [ "cognito-identity-demo", .product(name: "AWSCognitoIdentity", package: "AWSSwiftSDK"), ] ),

The main program code

The main program containing the code that gets executed automatically at runtime, is found in the file Source/main.swift. This file begins by importing the Swift modules needed to compile and link the program.

import Foundation import AWSCognitoIdentity @testable import CognitoIdentityDemo

The import statement is used to import the dependencies.

  • Foundation is the standard Apple Foundation package

  • AWSCognitoIdentity is the Amazon Cognito Identity module

  • CognitoIdentityDemo contains the demo’s code that accesses the AWS SDK for Swift. This includes the @testable attribute, which increases the access level of the classes and class members defined in the file CognitoIdentityDemo.swift. This lets the CognitoIdentityDemo class and its members be directly accessed by other modules, such as the tests.

The main program follows next, starting by creating an instance of CognitoIdentityDemo to use for interacting with the SDK for Swift. If instantiation fails, an error is output using the Swift dump() statement, and the program exits.

do { let identityDemo = try CognitoIdentityDemo() identityDemo.getIdentityPoolID(name: "SuperSpecialPool", createIfMissing: true) { poolID in guard let poolID = poolID else { print("*** Unable to find or create SuperSpecialPool!") return } print("*** Found or created SuperSpecialPool with ID \(poolID)") } } catch { dump(error, name: "Error creating identity test object") exit(1) }

Next, we call the CognitoIdentityDemo method getIdentityPoolID() to fetch the ID of the identity pool named "SuperSpecialPool", with its createIfMissing flag set to true to request that the identity pool be created automatically if it’s not found.

If our completion handler receives a pool ID value of nil, the specified pool was not found and could not be created. Otherwise, the pool’s ID is printed to the console.

Using AWS services from Swift code

In this example, all calls to the SDK come from the CognitoIdentityDemo class, found in the CognitoIdentityDemo.swift source file. This class’s initializer handles creating the CognitoIdentityClient object that we’ll use to interface with Amazon Cognito Identity.

class CognitoIdentityDemo { let cognitoIdentityClient: CognitoIdentityClient init() throws { cognitoIdentityClient = try CognitoIdentityClient() } }

Getting the ID of an existing identity pool

There are two versions of the CognitoIdentityDemo class’s getIdentityPoolID() method; in both cases, they look for and return the ID of an identity pool with the given name. In this first form of getIdentityPoolID(), if no identity pool with the specified name is found, nil is returned.

func getIdentityPoolID(name: String, completion: @escaping (String?) -> Void) throws { var token: String? = nil var poolID: String? = nil repeat { var error: Error? let listPoolsInput = ListIdentityPoolsInput(maxResults: 25, nextToken: token) cognitoIdentityClient.listIdentityPools(input: listPoolsInput) { (result) in switch(result) { case .success(let output): poolID = output.identityPools? .filter { $0.identityPoolName == name } .map { $0.identityPoolId } .first ?? nil token = output.nextToken case .failure(let listError): error = listError } } if error != nil { throw(error!) } } while token != nil && poolID == nil completion(poolID) }

Since the SDK’s CognitoIdentityClient method listIdentityPools() returns identity pools in pages, we use a loop to iterate over them until all pages have been returned.

A ListIdentityPoolsInput object is created to contain the parameters for listIdentityPools(), specifying a maximum number of pools per page of 25, and passing in the token, which is a string that’s returned by each iteration of listIdentityPools() to let the next iteration know where the last iteration left off.

If the result indicates success, the list of pools is found in the success object’s output property. We filter the output to eliminate pools whose name doesn’t match the given one and map the results to be simply the ID of the matching identity pool or pools. Then we set poolID to the first value in that list of IDs or nil if the list of matching pool IDs is empty. Finally, the output object’s nextToken value is stored in token so the SDK knows where to resume fetching identity pools the next time listIdentityPools() is called.

If an error occurs, we throw the error out to the caller. Otherwise, we call the specified completion handler, passing in the value of poolID, which will be either the first matching identity pool or nil if no match was found.

Finally, we fetch the iteration identifying token from the output’s nextToken property. This token will be passed into the next iteration of the loop to allow the API to continue from where it left off.

The loop continues until the iteration token is nil, which indicates that the current page of results is the last one. The found pool’s ID — or nil if the pool isn’t found — is then delivered to the completion handler.

Creating an identity pool if it doesn’t exist

The second form of getIdentityPoolID() accepts a second parameter, createIfMissing. If the specified identity pool name isn’t found and createIfMissing is true, this method creates a new identity pool with that name. In either case, the return value is the pool ID (or nil if the pool can’t be found or created for any reason).

func getIdentityPoolID(name: String, createIfMissing: Bool = false, completion: @escaping (String?) -> Void) throws { do { try self.getIdentityPoolID(name: name) { foundID in if let foundID = foundID { completion(foundID) } else if createIfMissing { self.createIdentityPool(name: name, completion: completion) } else { completion(nil) } } } catch { throw(error) } }

This works by first passing the given name into the first form of getIdentityPoolID() to see if the pool already exists. If it does, its ID is delivered to the completion handler. Otherwise, if createIfMissing is true, a method called createIdentityPool() is called to create the new pool. The completion handler is passed to that function so it can handle calling the completion handler with the resulting pool ID.

If the pool isn’t found and createIfMissing is false, the completion handler is called with a value of nil to indicate failure. Any other errors are thrown as exceptions to the caller.

Creating a new identity pool

The method createIdentityPool() creates a new identity pool whose name is given by the name input parameter. The newly-created pool’s ID is then returned to the caller. If an error occurs, the method returns nil. The intent of this function is to simplify the act of creating a pool by handling the values of many of the input parameters to the Amazon Cognito Identity method createIdentityPool().

func createIdentityPool(name: String, completion: @escaping (String?) -> Void) { let cognitoInputCall = CreateIdentityPoolInput( developerProviderName: "com.exampleco.CognitoIdentityDemo", identityPoolName: name ) cognitoIdentityClient.createIdentityPool(input: cognitoInputCall) { result in switch(result) { case .success(let output): if let poolID = output.identityPoolId { completion(poolID) } case .failure(let error): completion(nil) } } }

Since this method is just shorthand for calling the CognitoIdentityClient object’s createIdentityPool() method, there isn’t much to this code. First, a SDK for Swift CreateIdentityPoolInput object is created, containing the input values to be used when creating the new identity pool.

Once that’s set up, the SDK for Swift method cognitoIdentityClient.createIdentityPool() is called to actually send the request to the API and handle the response. If successful, the output object’s identityPoolId is sent to the completion handler as the newly-created identity pool’s ID. Otherwise, nil is sent to the completion handler.

Adding the AWS SDK for Swift to an existing Xcode project

If you have an existing Xcode project, you can add the SDK for Swift to it by opening your project’s main configuration pane and clicking on the "Swift Packages" tab at the right end of the pane’s tab bar. Let’s take a look at how this is done for an Xcode project called "Supergame," which plans to use Amazon S3 to obtain game data from a server.

The "Swift Packages" tab in Xcode


            The location of the "Swift Packages" tab in Xcode.

This shows a list of the Swift packages currently in use by your project. If you haven’t previously added any Swift packages, the list will be empty, as shown above. To add the AWS SDK for Swift package to your project, click on the "+" button just below the package list.

Finding and selecting packages to import


            The package selection dialog box in Xcode.

This opens up a window you can use to specify the package or packages you wish to add to your project. The window offers the option to select among standard Apple-provided packages, but you can also enter the URL of a custom package directly into the search box at the top of the window. The URL of the AWS SDK for Swift is https://github.com/awslabs/aws-sdk-swift.git .

Upon entering the SDK’s URL, you will see options to configure version requirements and other options for the SDK package import.

Configuring dependency rules for the AWS SDK for Swift package


            The Xcode dependency rule configuration panel during package import.

Configure the dependency rule, ensure that "Add to Project" is set to your project — "Supergame" in this case — and click "Add Package" to add the package to your project. This will show a window with a progress bar while the SDK and all its dependencies are processed and retrieved.

Fetching the AWS SDK for Swift package and its product list


            The Xcode "Verifying aws-sdk-swift" package window.

Next, you’ll be asked to select the specific products from the AWS SDK for Swift package that you wish to include in your project. Each of these products is generally one AWS API or service. Each package is listed by package name, starting with "AWS" and followed by the shorthand name of the service or toolkit.

In this case, the Supergame project is going to use Amazon S3, DynamoDB, and Amazon GameLift, so check the "AWSS3", "AWSDynamoDB", and "AWSGameLift", assign them to the correct target (iOS in this example), and click the "Add Package" button.

Choosing package products to select specific AWS services and toolkits


            The "Choose Package Products" dialog box in Xcode.

Your project is now configured to import the AWS SDK for Swift package and to include the desired APIs in the build for that target. If you open the target’s General tab and scroll down to "Frameworks, Libraries, and Embedded Content", you’ll see the AWS libraries listed.

The AWS SDK for Swift libraries in the Xcode target


            The Xcode target’s list of libraries with AWS libraries included

If your project is a multi-platform project, you also need to add the AWS libraries to the other targets in your project. For each platform’s target, navigate to the "Frameworks, Libraries, and Embedded Content" section of its General tab and click the "+" button to open the library picker window.

You can then either scroll to find and select all of the needed libraries and click the "Add" button to add them all to the target at once, or you can search for and select each library, select it, and click the "Add" button to add it to the target one at a time.

Finding and adding AWS SDK for Swift libraries using the Xcode library picker window


            Adding AWS SDK for Swift libraries using the Xcode library picker.

You’re now ready to import the libraries and any needed dependencies into individual Swift source code files and start using the AWS services in your project. Build your project as usual using the Xcode "Build" option in the Product" menu.

Building and running an SPM project

To build and run a project built using the Swift Package Manager from a Linux or macOS terminal prompt, use the following commands.

Building a project

$ swift build

Running a project

$ swift run $ swift run <executable-name> $ swift run <executable-name> <arg1, ...>

If your project builds only one executable file, you can simply type swift run to build and run it. If your project outputs multiple executables, you can specify the filename of the executable you want to run. If you want to pass arguments to the program when you run it, you must specify the executable name before listing the arguments.

Getting the built product output directory

$ swift build --show-bin-path /home/janice/MyProject/.build/x86_64-unknown-linux-gnu/debug

Deleting build artifacts

$ swift package clean

Deleting build artifacts and all build caches

$ swift package reset

Importing SDK for Swift libraries into source files

Once the libraries are in place, you can use the Swift import directive to import the individual libraries into each source file that needs them. Since Swift doesn’t automatically import dependencies, you will often need to import other files as well the one for the service you’re using. For instance, the ClientRuntime and AWSClientRuntime libraries define many of the types and base classes relied upon by higher-level SDK libraries, so you will often need them as well.

To that end, it’s often best practice to simply include them when importing AWS libraries:

import Foundation import ClientRuntime import AWSClientRuntime import AWSS3

While not always necessary, getting into this habit can prevent unnecessary debugging later.

Additional information