You are on page 1of 57

iOS Apps with REST APIs

Building Web-Driven Apps in Swift

Christina Moulton
This book is for sale at http://leanpub.com/iosappswithrest

This version was published on 2016-12-02

This is a Leanpub book. Leanpub empowers authors and publishers with the Lean Publishing
process. Lean Publishing is the act of publishing an in-progress ebook using lightweight tools and
many iterations to get reader feedback, pivot until you have the right book and build traction once
you do.

2015 - 2016 Teak Mobile Inc. All rights reserved. Except for the use in any review, the
reproduction or utilization of this work in whole or in part in any form by any electronic,
mechanical or other means is forbidden without the express permission of the author.
Tweet This Book!
Please help Christina Moulton by spreading the word about this book on Twitter!
The suggested hashtag for this book is #SwiftRestAppsBook.
Find out what other people are saying about the book by clicking on this link to search for this
hashtag on Twitter:
https://twitter.com/search?q=#SwiftRestAppsBook
Contents

1. From JSON API to Swift App 1


1.1 What Will You Be Able to Do? 1
1.2 Who Is This Book For? 2
1.3 Who Is This Book Not For? 2
1.4 Using This Book 2
1.5 What We Mean By Web Services / APIs / REST / CRUD 3
1.6 JSON 3
1.7 Versions 4
1.8 Source Code 4
1.9 Disclaimer 4
1.10 Trademarks 4

2. Our Apps Requirements 6


2.1 Match Tasks to Endpoints 7
2.2 User Interface 9
2.3 API Requirements 10
2.4 Make a Plan 11

3. Swift JSON Parsing & Networking Calls 101 12


3.1 REST API Calls with URLSession 12
3.2 REST API Calls with Alamofire 23
3.3 Alamofire Router 29
3.4 Strongly Typed GET and POST Calls with Alamofire 36
3.5 And Thats All 46

A Brief Introduction to CocoaPods 47


Adding a CocoaPod to a Project 47
What Does the Podfile Mean? 49
Dependencies 50
CocoaPods Version Numbers 50
Updating CocoaPods 51

Thanks for Reading 52


1. From JSON API to Swift App
You need to build an iOS app around your teams API or integrate a third party API. You need a quick,
clear guide to demystify Xcode and Swift. No esoteric details about Core Anything or mathematical
analysis of flatMap. Only the nitty gritty that you need to get real work done now: pulling data from
your web services into an iOS app, without tossing your MacBook or Mac Mini through a window.
You just need the bare facts on how to get CRUD done on iOS. Thats what this book will do for
you.

1.1 What Will You Be Able to Do?


After reading this book youll be able to:

Analyze a JSON response from a web service call and write Swift code to parse it into model
objects
Display those model objects in a table view so that when the user launches the app they have
a nice list to scroll through
Add authentication to use web service calls that require OAuth 2.0, a username/password, or
a token
Transition from the main table view to a detail view for each object, possibly making another
web service call to get more info about the object
Let users add, modify and delete objects (as long as your web service supports it)
Hook in to more web service calls to extend you app, like adding user profiles or letting users
submit comments or attach photos to objects

To achieve those goals well build out an app based on the GitHub API, focusing on gists. If youre
not familiar with gists, theyre basically just text snippets, often code written a GitHub user, here
are mine: CMoulton gists. Your model objects might be bus routes, customers, chat messages, or
whatever kind of object is core to your app. Well start by figuring out how to make API calls in
Swift then well start building out our app one feature at a time to make it more and more useful to
users:

Show a list of all public gists in a table view


Load more results when the user scrolls down
Let them pull to refresh to get the latest public gists
https://gist.github.com/cmoulton/

1
From JSON API to Swift App 2

Load images from URLs into table view cells


Use OAuth 2.0 for authentication to get lists of private and starred gists
Have a detail view for each gist showing the text
Allow users to add new gists, star and unstar gists, and delete gists
Handle not having an internet connection with warnings to the user and saving the gists on
the device

1.2 Who Is This Book For?


Software developers getting started with iOS but experienced in other languages
Front-end devs looking to implement native UIs for iOS apps (no CSS, oh noes!)
Back-end devs tasked with getting the data into the users hands on iOS
Android, Windows Phone, Blackberry, Tizen, Symbian & Palm OS devs looking to expand
their web service backed apps to iOS
Anyone whose boss is standing over their shoulder asking why the API data isnt showing up
in the table view yet

1.3 Who Is This Book Not For?


Complete newcomers to programming. You should have a decent grasp of at least one object-
oriented programming language or have completed several intro to iOS tutorials
Designers, managers, UX pros, Its a programming book. All the monospace font inserts
will probably drive you crazy
Cross-platform developers dedicated to their tools (like React Native & Xamarin). This book
is all Swift & native UI, all the time
Programmers building apps that have little or no web service interaction
Game devs, unless youre tying in a REST-like API

1.4 Using This Book


This book is mostly written as a tutorial in implementing the gists demo app. Depending on how
you learn best and how urgently you need to implement your own app, there are two different
approaches you might take:

1. Work through the tutorials as written, creating an app for GitHub Gists. Youll understand
how that app works and later be able to apply it to your own apps.
From JSON API to Swift App 3

2. Read through the tutorials but implement them for your own app and API. Throughout the
text Ill point out where youll need to analyze your own requirements and API to help you
figure out how to modify the example code to work with your API. Those tips will look like
this:

List the tasks or user stories for your app. Compare them to the list for the gists app, focusing
on the number of different objects (like stars, users, and gists) and the types of action taken
(like viewing a list, viewing an objects details, adding, deleting, etc.).

Well start with that task in the next chapter. Well analyze our requirements and figure out just what
were going to build. Then well start building the gists app, right after an introduction to making
network calls and parsing JSON in Swift.

1.5 What We Mean By Web Services / APIs / REST /


CRUD
Like anything in tech there are plenty of buzzwords around web services. For a while it was really
trendy to say your web services were RESTful. If you want to read the theory behind it, head over
to Wikipedia. For our purposes in this book, all I mean by REST web service or even when I say
web service or API is that we can send an HTTP request and we get back some data in a format
thats easy to use in our app. Usually that format will be JSON.
Web services are wonderful since they let you use existing web-based systems in your own apps.
Theres always a bit of a learning curve when youre using any with any web service for the
first time. Every one has its own quirks but most of the integration is similar enough that we can
generalize how to integrate them into our iOS apps.
If you want an argument about whether or not a web service is really RESTful youre not going to
find it here. Weve got work that just needs to get done.

1.6 JSON
In this book were going to deal with web services that return JSON. JSON is hugely common these
days so its probably what youll be dealing with. Of course, there are other return types out there,
like XML. This book wont cover responses in anything but JSON but it will encapsulate the JSON
parsing so that you can replace it with whatever you need to without having to touch a ton of code.
If you are dealing with XML response you should look at NSXMLParser.
https://en.wikipedia.org/wiki/Representational_state_transfer
http://www.json.org/
http://www.json.org/
https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Classes/NSXMLParser_Class/
From JSON API to Swift App 4

1.7 Versions
This is version 1.3 of this book. It uses Swift 3.0.1, iOS 10.1 (with support back to iOS 9.0), and Xcode
8.1. When we use libraries well explicitly list the versions used. The most commonly used one is
Alamofire 4.0.
If need want an older version of this book for Swift 2.2 or Swift 2.0, email me at christina@teakmobile.com
and I can send it to you.

1.8 Source Code


All sample code is available on GitHub under the MIT license. Links are provided throughout the
text. Each chapter has a tag allowing you to check out the code in progress up to the end of that
chapter.
Individuals are welcome to use code for commercial and open-source projects. As a courtesy, please
provide attribution to Teak Mobile Inc. or Christina Moulton. For more information, review the
complete license agreement in the GitHub repo.

1.9 Disclaimer
The information provided within this eBook is for general informational purposes only. The author
has made every effort to ensure the accuracy of the information within this book was correct at
time of publication. Teak Mobile Inc. and/or Christina Moulton do not assume and hereby disclaims
any liability to any party for any loss, damage, or disruption caused by errors or omissions, whether
such errors or omissions result from accident, negligence, or any other cause.
Teak Mobile Inc. and/or Christina Moulton shall in no event be liable for any loss of profit or any
other commercial damage, including but not limited to special, incidental, consequential, or other
damages.
Any use of this information is at your own risk.

1.10 Trademarks
This book identifies product names and services known to be trademarks, registered trademarks, or
service marks of their respective holders. They are used throughout this book in an editorial fashion
only. In addition, terms suspected of being trademarks, registered trademarks, or service marks have
mailto:christina@teakmobile.com
https://github.com/cmoulton/grokSwiftREST_v1.3/
https://opensource.org/licenses/MIT
https://github.com/cmoulton/grokSwiftREST_v1.3/blob/master/LICENSE.txt
From JSON API to Swift App 5

been appropriately capitalized, although Teak Mobile Inc. and Christina Moulton cannot attest to
the accuracy of this information. Use of a term in this book should not be regarded as affecting the
validity of any trademark, registered trademark, or service mark. Teak Mobile Inc. and/or Christina
Moulton are not associated with any product or vendor mentioned in this book.
Apple, Xcode, App Store, Cocoa, Cocoa Touch, Interface Builder, iOS, iPad, iPhone, Mac, OS X, Swift,
and Xcode are trademarks of Apple, Inc., registered in the United States and other countries.
GitHub is a trademark of GitHub, Inc. registered in the United States.
Mashape is a trademark of Mashape, Inc. registered in the United States.
2. Our Apps Requirements
Its always tempting to jump right into coding but it usually goes a lot smoother if we plan it out in
advance. At the least we need some idea of what were building. Lets lay that out for the gists app
and you can modify it to suit your app.
The first thing to do is to figure out what screens or views our app will have. There are a few ways
to do this task but I prefer to make a list of things that users will want to do with your app then
design the screens to make those things easy.
So what do people do with gists? Gists are snippets of text, often bits of code that are easily shared.
So people might:

1. Look at a list of public gists to see whats new


2. Search for interesting gists, maybe by programming language
3. Star a gist so they can find it later
4. Look at a list of gists theyve starred
5. Look at a list of their own gists to grab code they commonly use but dont want to retype all
the time
6. Look at details for a gist in a list (public, their own, or starred)
7. Create a new gist
8. Delete one of their gists

List the tasks or user stories for your app. Compare them to the list for the gists app, focusing
on the number of different objects (like stars, users, and gists) and the types of action taken
(like viewing a list, viewing an objects details, adding, deleting, etc.).

You might end up with a really long list. Consider each item and whether its really necessary for the
first version of your app. Maybe it can be part of the next release if the first one gets some traction?

Evaluate each task on your list. Decide which ones will form v1.0 of your app. You might
even want to design v2.0 now so youre not tempted to put everything in the first version.
A good shipped app is far better than a perfect app thats indefinitely delayed.

6
Our Apps Requirements 7

2.1 Match Tasks to Endpoints


Next look at each of those tasks and figure out how you can use the API to accomplish them or to get
the data youll need to display. Well check the documentation for the GitHub gists API to find the
endpoint for each task. Well make notes of anything special that we need to do, like authentication
or pagination.

2.1.1 List Public Gists


GET /gists/public

No authentication required. Will be paginated so well have to load more results if they want to see
more than 20 or so.

2.1.2 Search Gists


Hmm, there isnt an API for searching gists. Is our app still useful without search? I think so, so we
dont need to abandon the project.

2.1.3 Star/Unstar a Gist


PUT /gists/:id/star
DELETE /gists/:id/star

Requires authentication.

2.1.4 List Starred Gists


GET /gists/starred

Requires authentication.

2.1.5 List my Gists


There are two ways to get a list of a users gists:

GET /users/:username/gists

Or, if authenticated:
https://developer.github.com/v3/gists/
Our Apps Requirements 8

GET /gists

2.1.6 View Gist Details


Well probably be able to pass the data from the list of gists to the detail view but if we cant then
we can get a single gists details:

GET /gists/:id

If we want to display whether a gist is starred then we can use:

GET /gists/:id/star

2.1.7 Create Gist


POST /gists

Requires authentication to create a gist owned by a user. Otherwise the gist is created anonymously.
The JSON to send to create a gist looks like:

{
"description": "the description for this gist",
"public": true,
"files": {
"file1.txt": {
"content": "String file content"
}
}
}

2.1.8 Delete Gists


DELETE /gists/:id

Requires authentication.
Those are the endpoints for our tasks. Other than not being able to build our search feature, we
shouldnt have any trouble building our demo app around this API.

Analyze each action and list the API endpoint or iOS feature that will be needed for it.
Make sure that everything is possible using the API thats available. If not and the API is
being built by your team then request what you need now so theres plenty of time to get
it implemented.
Our Apps Requirements 9

2.2 User Interface


Now we have to figure out how were going to make the app usable by the users. Lets look at each
task and figure out how wed like it to work. Ive reordered the tasks below a bit to group together
bits that will share parts of the interface.

2.2.1 Authentication Flow


Since there isnt much they can do in the app without being logged in, well check at launch if theyre
authenticated. If not well start the login process right away.
If your API provides lots of functionality without authentication then you might want to delay
requiring the user to log in. If thats the case you can add the authentication checks before making
the API calls that require authentication.

2.2.2 List Public Gists


On launch the user sees a list (table view) with the public gists.

2.2.3 List Starred Gists


From the public gists the user can switch to a similar list of my starred gists.

2.2.4 List My Gists


From the public or starred gists the user can switch to a similar list of their own gists.
Sounds like well be able to use a single table view and have a selector so the user can pick which
of the three lists of gists they want to view.

2.2.5 View Gist Details


When they tap on a gist in one of the lists well transition to a different view. That view will list
details about the gist (description and filenames) and let them view the text of the files. Itll also
show whether weve starred the gist.

2.2.6 Star/Unstar a Gist


Within a gists detail view well show the starred status. They will be able to tap to star or unstar a
gist in that view.
Our Apps Requirements 10

2.2.7 Create Gist


On the list of My Gists well have a + button in the upper right corner. Itll display a form where
they can enter the info for the new gist:

Description: text
isPublic: Boolean
Filename: text
File content: text

To keep it simple well only allow a single file in gists created in the app in v1.0.

2.2.8 Delete Gists


Well allow swipe to delete on the list of My Gists.

Go through your tasks and figure out the user interface that people will use to accomplish
those tasks.

2.3 API Requirements


Well have some requirements to interact with the API that arent obvious when we consider the
users tasks. But reading through the documentation carefully can help us make a list.

2.3.1 Authentication
You can read public gists and create them for anonymous users without a token;
however, to read or write gists on a users behalf the gist OAuth scope is required.
GitHub Gists API docs

So well need to set up authentication, preferably OAuth 2.0, including the gist scope. The API will
work with a username/password but then wed have to worry about securing that data. With OAuth
2.0 we never see the username & password, only the token for our app.
We will store the OAuth token securely.

Check your APIs authentication requirements. In the auth chapters well cover how to im-
plement OAuth 2.0, token-based authentication, and basic auth with username/password.

https://developer.github.com/v3/gists/#authentication
Our Apps Requirements 11

2.3.2 Handle App Transport Security


In iOS 9 Apple introduced Apples App Transport Security. ATS requires SSL to be used for
transferring data and its pretty picky about just how its implemented. Sadly this means that a
lot of servers out there dont meet ATSs requirements. GitHubs gist API complies with the ATS
requirements so we wont have to add an exception.

If you find that you get SSL errors when calling your API from iOS 9 then youll probably
need to add an exception to ATS. See the Networking 101 chapter for details on adding that
exception. You can use the code in that chapter to try some simple API calls to your server
to see if you get SSL errors.

2.4 Make a Plan


Now that we know what we need to do we can figure out how were going to do it. Well build the
app up incrementally, feature by feature:

Set up the app with a table view displaying the public gists
Add custom HTTP headers
Load images in table view cells
Load more gists when they scroll down
Add pull to refresh
Add authentication and let them switch to displaying My Gist and Starred Gists
Create a detail view for the gists
Add starring & unstarring gists in the detail view
Add deleting and creating gists
Handle not having an internet connection

Put your views and tasks in order to implement them. Try to match up roughly with the
order for the gists app. If you dont have an API call to start with that doesnt require
authentication you might need to jump ahead to the auth chapters before starting on the
table view chapter. If your API requires custom headers to be sent with all requests then
youll want to start with the headers chapter then come back to the table view chapter.

Now that weve sorted out the basic requirements for our app we know where to start. First well
spend a little time looking at how to make web requests and parse JSON in Swift so we dont get
bogged down with those details later.
https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/
uid/TP40009251-SW33
3. Swift JSON Parsing & Networking
Calls 101
Pretty much every app these days consumes or creates content through an API. In this book well
mostly use Alamofire, a rich networking library, to interact with web services but you can also use
iOSs URLSession to make REST calls.

3.1 REST API Calls with URLSession


The function to use to make an async URL request is part of URLSession:

open func dataTask(with request: URLRequest,


completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void
) -> URLSessionDataTask

It takes a request which contains the URL then goes off and sends the request. Once it gets a response
(or has an error to report), the completion handler gets called. The completion handler is where
we can work with the results of the call: error checking, saving the data locally, updating the UI,
whatever.
The simplest case is a GET request. Of course, we need an API to hit. Fortunately theres super handy
JSONPlaceholder:

JSONPlaceholder is a fake online REST API for testing and prototyping. Its like image
placeholders but for web developers.

JSONPlaceholder has a handful of resources similar to what youll find in a lot of apps: users, posts,
photos, albums, Well stick with Todos.

Youll need to create an Xcode project to run the demo code in this chapter. To do that, open
Xcode and select File -> New -> Project. Choose a Single View Application. Give it a name (maybe
Grok101), make sure its using Swift (not Objective-C), and choose where you want to save it.

https://github.com/Alamofire/Alamofire
https://jsonplaceholder.typicode.com

12
Swift JSON Parsing & Networking Calls 101 13

First lets print out the title of the first todo (assuming that there are todos, which this dummy API
already has). To get a single todo, we need to make a GET call to the todos endpoint with an ID
number. Checking out https://jsonplaceholder.typicode.com/todos/ we can see that the id for the
first todo is 1. So lets grab it:
First, set up the URL request:

let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)

Not sure where to put that code? If you created a test project at the start of this chapter, open the
ViewController.swift file. You can put the test code in viewDidLoad like this:

import UIKit

class ViewController: UIViewController {


override func viewDidLoad() {
super.viewDidLoad()
// test code goes here like this:
let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"
guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
// ... keep adding test code here
}
}

Now your code will be run when that view controller is shown, which happens right after your
app launches.

The guard statement lets us check that the URL weve provided is valid.
Then we need a URLSession to use to send the request, we can use the default shared session:

https://jsonplaceholder.typicode.com/todos/
Swift JSON Parsing & Networking Calls 101 14

let session = URLSession.shared

Then create the data task:

let task = session.dataTask(with: urlRequest, completionHandler:{ _, _, _ in })

{ _, _, _ in } looks funny but its just an empty completion handler. Since we just want to execute
our request and not deal with the results yet, we can specify an empty completion handler here. It
has to have input arguments to match the type of completion handler thats expected, hence the _,
_, _ placeholders for those three arguments.

And finally send it (yes, this is an oddly named function):

task.resume()

Calling this now will hit the URL (from the urlRequest) and obtain the results (using a GET request
since thats the default). To actually get the results to do anything useful we need to implement the
completion handler.
Completion handlers can be a bit confusing the first time you run in to them. On the one hand,
theyre a variable or argument but, on the other hand, theyre a chunk of code. Weird if youre not
used to that kind of thing (a.k.a., closures).

Closures are self-contained blocks of functionality that can be passed around and used
in your code. - iOS Developer Documentation on Closures

Completion handlers are super convenient when your app is doing something that might take a
little while, like making an API call, and you need to do something when that task is done, like
updating the UI to show the data. Youll see completion handlers in Apples APIs like dataTask(with
request: completionHandler:) and later on well add some of our own completion handlers when
were building out our API calls.
In dataTask(with request: completionHandler:) the completion handler argument has a signa-
ture like this:

completionHandler: (Data?, URLResponse?, Error?) -> Void

The completion handler takes a chunk of code with three arguments: (Data?, URLResponse?,
Error?) that returns nothing: Void.

To specify a completion handler we can write the closure inline like this:
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html
Swift JSON Parsing & Networking Calls 101 15

let task = session.dataTask(with: urlRequest,


completionHandler: { (data, response, error) in
// this is where the completion handler code goes
})
task.resume()

The code for the completion handler is the bit between the curly brackets. Notice that the three
arguments in the closure (data, response, error) match the arguments in the completion handler
declaration: (Data?, URLResponse?, Error?). You can specify the types explicitly when you create
your closure but its not necessary because the compiler can figure it out:

let task = session.dataTask(with: urlRequest, completionHandler:


{ (data: Data?, response: URLResponse?, error: Error?) in
// this is where the completion handler code goes
if let response = response {
print(response)
}
if let error = error {
print(error)
}
})
task.resume()

Somewhat confusingly, you can actually drop the completionHandler: bit and just tack the closure
on at the end of the function call. This is totally equivalent to the code above and a pretty common
syntax youll see in Swift code:

let task = session.dataTask(with: urlRequest) { (data, response, error) in


// this is where the completion handler code goes
if let response = response {
print(response)
}
if let error = error {
print(error)
}
}
task.resume()

Well be using that trailing closure syntax in the rest of our code. You can use a trailing closure
whenever the last argument for a function is a closure.
https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/Closures.html#//apple_ref/doc/
uid/TP40014097-CH11-ID102
Swift JSON Parsing & Networking Calls 101 16

If you want to ignore some arguments you can tell the compiler that you dont want them by
replacing them with _ (like we did earlier when we werent ready to implement the completion
handler yet). For example, if we only need the data and error arguments but not the response in
our completion handler:

let task = session.dataTask(with: urlRequest) { (data, _, error) in


// can't do print(response) since we don't have response
if let error = error {
print(error)
}
}
task.resume()

We can also declare the closure as a variable then pass it in when we call session.dataTask(with:).
Thats handy if we want to use the same completion handler for multiple tasks. We will use this
technique when implementing an OAuth 2.0 login flow, since it has lots of steps but we will want
to handle any of them failing similarly.
Heres how you can use a variable for a completion handler:

let myCompletionHandler: (Data?, URLResponse?, Error?) -> Void = {


(data, response, error) in
// this is where the completion handler code goes
if let response = response {
print(response)
}
if let error = error {
print(error)
}
}
let task = session.dataTask(with: urlRequest, completionHandler: myCompletionHandler)
task.resume()

So thats how you specify a completion handler. But when we run that code whatll happen to our
closure? It wont get called right away when we call dataTask(with request: completionHan-
dler:). Thats good thing, if it were called immediately then we wouldnt have the results of the
web service call yet.
Somewhere in Apples implementation of that function it will get called like this:
Swift JSON Parsing & Networking Calls 101 17

open func dataTask(with request: URLRequest,


completionHandler: @escaping (Data?, URLResponse?, Error?) -> Swift.Void)
-> URLSessionDataTask {
// make an URL request
// wait for results
// check for errors and stuff
completionHandler(data, response, error)
// return the data task
}

You dont need to write that in your own code, its already implemented in dataTask(with:
completionHandler:). In fact, there are probably a few calls like that for handling success and error
cases. The completion handler will just sit around waiting to be called whenever dataTask(with:
completionHandler:) is done.

So whats the point of completion handlers? Well, we can use them to take action when something
is done. Like here we could set up a completion handler to print out the results and any potential
errors so we can make sure our API call worked. Lets go back to our dataTask(with request:
completionHandler:) example and implement a useful completion handler. Heres where the code
will go:

let task = session.dataTask(with: urlRequest) { data, response, error in


// do stuff with response, data & error here
}
task.resume()

Now we have access to three arguments: the data returned by the request, the URL response, and an
error (if one occurred). So lets check for errors and figure out how to get at the data that we want:
the first todos title. We need to:

1. Make sure we got data and no error


2. Try to transform the data into JSON (since thats the format returned by the API)
3. Access the todo object in the JSON and print out the title
Swift JSON Parsing & Networking Calls 101 18

let task = session.dataTask(with: urlRequest) {


(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])
as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
// now we have the todo
// let's just print it to prove we can access it
print("The todo is: " + todo.description)

// the todo object is a dictionary


// so we just access the title using the "title" key
// so check for a title and print it if we have one
guard let todoTitle = todo["title"] as? String else {
print("Could not get todo title from JSON")
return
}
print("The title is: " + todoTitle)
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()

Which prints out:


Swift JSON Parsing & Networking Calls 101 19

The todo is: {


completed = 0;
id = 1;
title = "delectus aut autem";
userId = 1;
}
The title is: delectus aut autem

Its a little verbose but if you just need a quick GET call to an API without authentication, thatll do
it.
Converting the data to JSON is done using JSONSerialization.jsonObject(with: options:).
Well talk about JSON parsing more later when we create a class for the Todo objects. In a later
chapter well come back to it again to figure how to handle dates and more complex structures.
If you need a HTTP method type other than GET then you can set the HTTP method in the
URLRequest so you can set the method type. Since well have to do that after the URLRequest is
created, we need to declare it with var, not let. Set the code youve been working on aside (you can
just comment it out) so we can create a POST request instead:

let todosEndpoint: String = "https://jsonplaceholder.typicode.com/todos"


guard let todosURL = URL(string: todosEndpoint) else {
print("Error: cannot create URL")
return
}
var todosUrlRequest = URLRequest(url: todosURL)
todosUrlRequest.httpMethod = "POST"

Then we can set the new todo as the httpBody for the request:

let newTodo: [String: Any] = ["title": "My First todo", "completed": false, "userId": 1]
let jsonTodo: Data
do {
jsonTodo = try JSONSerialization.data(withJSONObject: newTodo, options: [])
todosUrlRequest.httpBody = jsonTodo
} catch {
print("Error: cannot create JSON from todo")
return
}

JSONSerialization.data(withJSONObject: options:) converts JSON that weve created as dic-


tionaries and arrays into data so we can send it as part of the urlRequest.
Now we can execute the request:
Swift JSON Parsing & Networking Calls 101 20

let session = URLSession.shared


let task = session.dataTask(with: todosUrlRequest) { _, _, _ in }
task.resume()

If its working correctly then we should get our todo back as a response along with the id number
assigned to it. Since its just for testing, JSONPlaceholder will let you do all sorts of REST requests
(GET, POST, PUT, PATCH, DELETE and OPTIONS) but it wont actually change the data based on
your requests. So when we send this POST request, well get a response with an ID to confirm that
we did it right but it wont actually be kept in the database so we cant access it on subsequent calls.
We can use the same error checking and parsing that we used with our GET request to make sure
the API call worked:

let todosEndpoint: String = "https://jsonplaceholder.typicode.com/todos"


guard let todosURL = URL(string: todosEndpoint) else {
print("Error: cannot create URL")
return
}
var todosUrlRequest = URLRequest(url: todosURL)
todosUrlRequest.httpMethod = "POST"
let newTodo: [String: Any] = ["title": "My First todo", "completed": false, "userId": 1]
let jsonTodo: Data
do {
jsonTodo = try JSONSerialization.data(withJSONObject: newTodo, options: [])
todosUrlRequest.httpBody = jsonTodo
} catch {
print("Error: cannot create JSON from todo")
return
}

let session = URLSession.shared

let task = session.dataTask(with: todosUrlRequest) {


(data, response, error) in
guard error == nil else {
print("error calling POST on /todos/1")
print(error)
return
}
guard let responseData = data else {
print("Error: did not receive data")
return
}

// parse the result as JSON, since that's what the API provides
Swift JSON Parsing & Networking Calls 101 21

do {
guard let receivedTodo = try JSONSerialization.jsonObject(with: responseData,
options: []) as? [String: Any] else {
print("Could not get JSON from responseData as dictionary")
return
}
print("The todo is: " + receivedTodo.description)

guard let todoID = receivedTodo["id"] as? Int else {


print("Could not get todoID as int from JSON")
return
}
print("The ID is: \(todoID)")
} catch {
print("error parsing response from POST on /todos")
return
}
}
task.resume()

Deleting is pretty similar (minus creating the JSON todo):

let firstTodoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


var firstTodoUrlRequest = URLRequest(url: URL(string: firstTodoEndpoint)!)
firstTodoUrlRequest.httpMethod = "DELETE"

let session = URLSession.shared

let task = session.dataTask(with: firstTodoUrlRequest) {


(data, response, error) in
guard let _ = data else {
print("error calling DELETE on /todos/1")
return
}
print("DELETE ok")
}
task.resume()

So thats how to call a REST API from Swift using URLSession. There are a couple of gotchas though:
Were assuming well get results and theyll be in the format we expect. Weve got error handling to
make sure the todo is valid JSON:
Swift JSON Parsing & Networking Calls 101 22

do {
guard let receivedTodo = try JSONSerialization.jsonObject(with: responseData,
options: []) as? [String: Any] else {
// ...
return
}
// handle valid todo
} catch {
print("error parsing response from POST on /todos")
return
}

That catch statement will catch anything that isnt valid JSON. In other words, any time JSONSe-
rialization.jsonObject(with: , options: ) cant convert the responseData into a valid Any.

Since the JSON parsing gives us an Any we then need to check that the JSON is a dictionary using:

as? [String: Any]

The guards else statement will get called if the JSON is valid but it isnt a [String: Any] dictionary.
That can happen if the JSON has an array at the top level. Weve checked that this API call should
return a dictionary so getting an array instead is something wed consider an error:

guard let receivedTodo = try JSONSerialization.jsonObject(with: responseData,


options: []) as? [String: Any] else {
// Got valid JSON but it isn't [String: Any]
print("JSON isn't a [String: Any]")
return
}
// Here we have valid JSON and it's a [String: Any]

So far the code to make the calls themselves is pretty verbose and the level of abstraction is low:
youre thinking about todos but having to code in terms of HTTP requests and data tasks. Alamofire
looks like a nice step up:

https://github.com/Alamofire/Alamofire
Swift JSON Parsing & Networking Calls 101 23

let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


Alamofire.request(todoEndpoint)
.responseJSON { response in
// get errors
if let error = response.result.error {
print(error)
}
// get serialized data (i.e., JSON)
if let value = response.result.value {
print(value)
}
// get raw data
if let data = response.data {
print(data)
}
// get NSHTTPURLResponse
if let httpResponse = response.response {
print(httpResponse)
}
}

In the next section well work out how to do the same requests we did using Alamofire so we can
use that more concise syntax.
Grab the code on GitHub: REST gists

3.2 REST API Calls with Alamofire


In the last section we looked at getting access REST APIs in iOS using URLSession. dataTask(with
request: completionHandler:) works just fine for simple cases, like a URL shortener. But these
days lots of apps have tons of web service calls that are just begging for better handling: a higher
level of abstraction, concise syntax, simpler streaming, pause/resume, progress indicators,
In Objective-C, this was a job for AFNetworking. In Swift, Alamofire is our option for elegance.
Just like in the previous section, well use the super handy JSONPlaceholder as our API.
Heres our GET request from last section where we grabbed the first todo and printed out its title:

https://gist.github.com/cmoulton/149b03b5ea2f4c870a44526a02618a30
https://github.com/afnetworking/afnetworking
https://github.com/Alamofire/Alamofire
https://jsonplaceholder.typicode.com
Swift JSON Parsing & Networking Calls 101 24

let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


guard let url = URL(string: todoEndpoint) else {
print("Error: cannot create URL")
return
}
let urlRequest = URLRequest(url: url)
let session = URLSession.shared
let task = session.dataTask(with: urlRequest) {
(data, response, error) in
// check for any errors
guard error == nil else {
print("error calling GET on /todos/1")
print(error!)
return
}
// make sure we got data
guard let responseData = data else {
print("Error: did not receive data")
return
}
// parse the result as JSON, since that's what the API provides
do {
guard let todo = try JSONSerialization.jsonObject(with: responseData, options: [])
as? [String: Any] else {
print("error trying to convert data to JSON")
return
}
// now we have the todo
// let's just print it to prove we can access it
print("The todo is: " + todo.description)
// the todo object is a dictionary
// so we just access the title using the "title" key
// so check for a title and print it if we have one
guard let todoTitle = todo["title"] as? String else {
print("Could not get todo title from JSON")
return
}
print("The title is: " + todoTitle)
} catch {
print("error trying to convert data to JSON")
return
}
}
task.resume()
Swift JSON Parsing & Networking Calls 101 25

Which is an awful lot of code for what were doing (but far less than back in the dark ages when
thousands of lines of code generated from WSDL web services would crash Xcode just by scrolling
the file). Theres no authentication and just enough error checking to get by.
Lets see how this looks with the Alamofire library that I keep talking up. First add Alamofire v4.0
to your project using CocoaPods. (See A Brief Introduction to CocoaPods if youre not sure how.
Dont forget to close the xcodeproj and open the xcworkspace instead.)
After adding the pod, youll need to import Alamofire into the file where youre working:

import UIKit
import Alamofire

class ViewController: UIViewController {


// ...
}

Now we can set up the request:

let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


Alamofire.request(todoEndpoint)
.responseJSON { response in
// ...
}

Were telling Alamofire to set up & send an asynchronous request to todoEndpoint (without the
ugly call to URL to wrap up the string). We dont have to explicitly say its a GET request since thats
the default HTTP method. If we wanted to specify the HTTP method then wed use a member of
Alamofires HTTPMethod enum, which includes .get, .post, .patch, .options, .delete, etc. We can
add the method when creating the request to make it explicit:

let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


Alamofire.request(todoEndpoint, method: .get)
.responseJSON { response in
// ...
}

Then we get the data (asynchronously) as JSON in the .responseJSON. We could also use .response
(for an NSHTTPURLResponse), .responsePropertyList, or .responseString (for a string). We can
even chain multiple .responseX methods for debugging:
Swift JSON Parsing & Networking Calls 101 26

let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


Alamofire.request(todoEndpoint)
.responseJSON { response in
// handle JSON
}
.responseString { response in
if let error = response.result.error {
print(error)
}
if let value = response.result.value {
print(value)
}
}

Thats neat but right now we just want to get the todos title from the JSON. Well make the request
then handle it with .responseJSON. Like last time we need to do some error checking:

1. Check for an error returned by the API call


2. If no error, see if we got any JSON results
3. Check for an error in the JSON transformation
4. If no error, access the todo object in the JSON and print out the title

.responseJSON takes care of some of the boilerplate we had to write earlier. It makes sure we got
response data then calls JSONSerialization.jsonObject for us. So we can just check that we got
the JSON object that we expected. In this case thats a dictionary so we use as? [String: Any] in
our guard statement.
When using the .responseX handlers in Alamofire, the value that you want (e.g., a string for
.responseString) will be be in response.result.value. If that value cant be parsed or theres
a problem with the call then youll get an error in response.result.error.
So to check for errors then get the JSON if there are no errors:

let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


Alamofire.request(todoEndpoint)
.responseJSON { response in
guard let json = response.result.value as? [String: Any] else {
print("didn't get todo object as JSON from API")
print("Error: \(response.result.error)")
return
}
print(json)
}

You can use response.result.isSuccess if you just need to know whether the call succeeded or
failed:
Swift JSON Parsing & Networking Calls 101 27

let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


Alamofire.request(todoEndpoint)
.responseJSON { response in
guard response.result.isSuccess else {
// handle failure
return
}
// handle success
}

Theres another possibility that our current code doesnt consider well: what if we didnt get an
error but we also didnt get any JSON or the JSON isnt a dictionary? JSON top-level objects can be
arrays as well as dictionaries. Lets split up the code that checks that we got the JSON we expected
and the code that checks for errors in to two separate guard statements. Some APIs will return error
messages as JSON thats a different format than what you requested. In those cases we need to
differentiate between not getting anything and not getting the JSON that we expected:

let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


Alamofire.request(todoEndpoint)
.responseJSON { response in
// check for errors
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling GET on /todos/1")
print(response.result.error!)
return
}

// make sure we got some JSON since that's what we expect


guard let json = response.result.value as? [String: Any] else {
print("didn't get todo object as JSON from API")
print("Error: \(response.result.error)")
return
}

print(json)
}

Finally we can extract the title from the JSON just like we did in the previous section:
Swift JSON Parsing & Networking Calls 101 28

let todoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


Alamofire.request(todoEndpoint)
.responseJSON { response in
// check for errors
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling GET on /todos/1")
print(response.result.error!)
return
}

// make sure we got some JSON since that's what we expect


guard let json = response.result.value as? [String: Any] else {
print("didn't get todo object as JSON from API")
print("Error: \(response.result.error)")
return
}

// get and print the title


guard let todoTitle = json["title"] as? String else {
print("Could not get todo title from JSON")
return
}
print("The title is: " + todoTitle)
}

To POST, we just need to change the HTTP method and provide the todo item as JSON data:

let todosEndpoint: String = "https://jsonplaceholder.typicode.com/todos"


let newTodo: [String: Any] = ["title": "My First Post", "completed": 0, "userId": 1]
Alamofire.request(todosEndpoint, method: .post, parameters: newTodo,
encoding: JSONEncoding.default)
.responseJSON { response in
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling POST on /todos/1")
print(response.result.error!)
return
}
// make sure we got some JSON since that's what we expect
guard let json = response.result.value as? [String: Any] else {
print("didn't get todo object as JSON from API")
print("Error: \(response.result.error)")
return
}
Swift JSON Parsing & Networking Calls 101 29

// get and print the title


guard let todoTitle = json["title"] as? String else {
print("Could not get todo title from JSON")
return
}
print("The title is: " + todoTitle)
}

And DELETE is nice and compact:

let firstTodoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


Alamofire.request(firstTodoEndpoint, method: .delete)
.responseJSON { response in
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling DELETE on /todos/1")
print(response.result.error!)
return
}
print("DELETE ok")
}

Grab the example code on GitHub.


So thats one step better on our journey to nice, clean REST API calls. But were still interacting with
untyped JSON which can easily lead to errors. Next well take another step towards cleaner code by
using an Alamofire Router to create the URL requests for us.

3.3 Alamofire Router


Previously we set up some REST API Calls With Alamofire. While its a bit of overkill for those
simple calls we can improve our code by using an Alamofire router. The router will compose the
URL requests for us which will avoid having URL strings and HTTP methods throughout our code.
A router can also be used to apply headers, e.g., for including an OAuth token or other authorization
header.
Using a router with Alamofire is good practice since it helps keep our code organized. The router
is responsible for creating the URL requests so that our API manager (or whatever makes the API
calls) doesnt need to do that along with all of the other responsibilities that it has. A good rule of
thumb is to split out code if you can explain what each new object will be responsible for.
https://gist.github.com/cmoulton/b6c3a8e6cc1e4d31abd0ea0bdc01039d
Swift JSON Parsing & Networking Calls 101 30

Our previous simple examples included calls to get, create, and delete todos using JSONPlaceholder.
JSONPlaceholder is a fake online REST API for testing and prototyping. Its like image placeholders
but for web developers.
Previously when we made networking calls with Alamofire we used code like this:

Alamofire.request(todoEndpoint)
Alamofire.request(todoEndpoint, method: .post, parameters: newTodo,
encoding: JSONEncoding.default)
Alamofire.request(firstTodoEndpoint, method: .delete)

Currently were providing the URL as a string and the HTTP method (unless its the default .get).
Instead of these two parameters Alamofire.request(...) can also take a URLRequestConvertible
object. To be a URLRequestConvertible object, we need to have an asURLRequest() fucntion that
returns the request we want to send. Thats what well take advantage of to create our router.

3.3.1 Using an Alamofire Router


To start well declare a router. Itll be an enum with a case for each type of call we want to make. A
convenient feature of Swift enums is that the cases can have arguments. For example, our .get case
can have an Int argument so we can pass in the ID number of the todo that we want to get.
Well also need the base URL for our API so we can use it to build up the URLRequest that we want
to send:

import Foundation
import Alamofire

enum TodoRouter: URLRequestConvertible {


static let baseURLString = "https://jsonplaceholder.typicode.com/"

case get(Int)
case create([String: Any])
case delete(Int)

func asURLRequest() throws -> URLRequest {


// TODO: implement
}
}

Well come back and implement the asURLRequest() function in a bit. First lets see how we need
to change our existing calls to use the router.
For the GET call all we need to do is to change:
https://jsonplaceholder.typicode.com
Swift JSON Parsing & Networking Calls 101 31

let todosEndpoint: String = "https://jsonplaceholder.typicode.com/todos"


Alamofire.request(todoEndpoint)

to

Alamofire.request(TodoRouter.get(1))

Dont forget delete the line defining todoEndpoint. All of the URL string handling is now done
within the router.
The .POST call is similar. Change:

let todosEndpoint: String = "https://jsonplaceholder.typicode.com/todos"


let newTodo = ["title": "My first todo", completed: false, "userId": 1]
Alamofire.request(todoEndpoint, method: .post, parameters: newTodo,
encoding: JSONEncoding.default)

to

let newTodo = ["title": "My first todo", completed: false, "userId": 1]


Alamofire.request(TodoRouter.create(newTodo))

You can see there that the router has abstracted away the encoding as well as the endpoint from this
function. The encoding is part of creating a URL request so it rightly belongs in the router, not the
code making the API calls.
And for the delete call, change:

let firstTodoEndpoint: String = "https://jsonplaceholder.typicode.com/todos/1"


Alamofire.request(firstTodoEndpoint, method: .delete)

to

Alamofire.request(TodoRouter.delete(1))

Now our calls are a bit easier to read.

3.3.2 Generating the URL Requests


The code in this section is pretty Swifty. If it looks a little odd at first just keep reading. Well work
through it and explain it as we go.
Within the router we need a function so that our calls like Router.delete(1) give us a URLRe-
quest that Alamofire.Request() knows how to use. Alamofire.Request() will recognize that
our enum is URLRequestConvertible so it has a asURLRequest() function, as required by that
protocol. So when we give Alamofire.Request() an enum like TodoRouter.get(1) then it can call
asURLRequest() to get the request to send. If youre wondering where this happens, right-click
Alamofire.request(...) in your code. Itll show you the definition for that function:
Swift JSON Parsing & Networking Calls 101 32

public func request(_ urlRequest: URLRequestConvertible) -> DataRequest {


return SessionManager.default.request(urlRequest)
}

Then right-click on request(urlRequest) in the body of that function to see what gets done with
it:

open func request(_ urlRequest: URLRequestConvertible) -> DataRequest {


do {
let originalRequest = try urlRequest.asURLRequest()
// ... create a task for the request, add it to a queue, and start it
return request
} catch {
return request(failedWith: error)
}
}

Right on the first line in that function it takes the urlRequest, in our case Todo.get(1), and calls
.asURLRequest() on it. So thats how the router works with Alamofire.request(...).

Youll need a separate case if the URL, HTTP method (like GET or POST), or argument type is
different. So within our router function we have three cases: .get, .create, and .delete:

enum TodoRouter: URLRequestConvertible {


static let baseURLString: String = "https://jsonplaceholder.typicode.com/"

case get(Int)
case create([String: Any])
case delete(Int)

func asURLRequest() throws -> URLRequest {


// TODO: implement
}
}

Weve also included the base URL since its the same in all of the calls. Thatll avoid the chance of
having a typo in that part of the URL for one of the calls.
Within the asURLRequest() function well need a few elements that well combine to create the url
request: the HTTP method, any parameters to pass, and the URL.
Since were using an enum, we can use a switch statement to define the HTTP methods for each
case:
Swift JSON Parsing & Networking Calls 101 33

var method: HTTPMethod {


switch self {
case .get:
return .get
case .create:
return .post
case .delete:
return .delete
}
}

Well need to add the parameters to post for the .create case:

let params: ([String: Any]?) = {


switch self {
case .get, .delete:
return nil
case .create(let newTodo):
return (newTodo)
}
}()

Swift enums can have arguments, its called value binding. So when we use the .create case we
can pass in the parameters (in this case, the dictionary of data for the new todo). Then we can access
it using:

case .myCase(let argument):

Were also using it to for the .get and .delete cases to pass in the ID numbers for the gists. Well
retrieve those when we need them to create the URL:

case .get(let number):

Now we can start to build up the URL request. First well need the URL. We have the base URL
added above so we can combine it with the relative path for each case to get the full URL:

https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/ControlFlow.html
Swift JSON Parsing & Networking Calls 101 34

let url: URL = {


// build up and return the URL for each endpoint
let relativePath: String?
switch self {
case .get(let number):
relativePath = "todos/\(number)"
case .create:
relativePath = "todos"
case .delete(let number):
relativePath = "todos/\(number)"
}

var url = URL(string: TodoRouter.baseURLString)!


if let relativePath = relativePath {
url = url.appendingPathComponent(relativePath)
}
return url
}()

We just set up the code to get the HTTP method, URL, and parameters for each case. Now we can
put them together to create a URL request:

var urlRequest = URLRequest(url: url)


urlRequest.httpMethod = method.rawValue

let encoding = JSONEncoding.default


return try encoding.encode(urlRequest, with: params)

First we create a mutable request using the URL. Its mutable because we declared it with var, not
let. Thats necessary so we can set the httpMethod on the next line. Then we encode any parameters
and add them to the request. This web service uses JSON, like most these days, so were using
JSONEncoding.default.

Finally, we return the request. Heres it all together:


Swift JSON Parsing & Networking Calls 101 35

import Foundation
import Alamofire

enum TodoRouter: URLRequestConvertible {


static let baseURLString = "https://jsonplaceholder.typicode.com/"

case get(Int)
case create([String: Any])
case delete(Int)

func asURLRequest() throws -> URLRequest {


var method: HTTPMethod {
switch self {
case .get:
return .get
case .create:
return .post
case .delete:
return .delete
}
}

let params: ([String: Any]?) = {


switch self {
case .get, .delete:
return nil
case .create(let newTodo):
return (newTodo)
}
}()

let url: URL = {


// build up and return the URL for each endpoint
let relativePath: String?
switch self {
case .get(let number):
relativePath = "todos/\(number)"
case .create:
relativePath = "todos"
case .delete(let number):
relativePath = "todos/\(number)"
}

var url = URL(string: TodoRouter.baseURLString)!


if let relativePath = relativePath {
Swift JSON Parsing & Networking Calls 101 36

url = url.appendingPathComponent(relativePath)
}
return url
}()

var urlRequest = URLRequest(url: url)


urlRequest.httpMethod = method.rawValue

let encoding = JSONEncoding.default


return try encoding.encode(urlRequest, with: params)
}
}

If you havent yet, create a new file called TodoRouter.swift and put the router code in it. Save and
test out our code. The console log should display the same todo titles and lack of errors that we had
in the previous section. Weve created a simple Alamofire router that you can adapt to your API
calls.
Heres the example code on GitHub.

3.4 Strongly Typed GET and POST Calls with Alamofire


Weve used Alamofire to make some REST requests to a web service. But were still passing JSON
around in our app. Lets improve that up by building a higher layer of abstraction by mapping the
JSON to a strongly typed class. Thatll keep our code better organized so we arent struggling to
keep too many details in our minds at once.
First, well need a class to represent the Todo objects were dealing with. Create a new class in its
own file to represent the Todo objects. It will have a few properties, an initializer to create new Todo
objects, and a description function to print out all of the properties, which is handy for debugging:

class Todo {
var title: String
var id: Int?
var userId: Int
var completed: Bool

required init?(title: String, id: Int?, userId: Int, completedStatus: Bool) {


self.title = title
self.id = id
self.userId = userId

https://github.com/cmoulton/grokAlamofireRouter
https://github.com/Alamofire/Alamofire
Swift JSON Parsing & Networking Calls 101 37

self.completed = completedStatus
}

func description() -> String {


return "ID: \(self.id), " +
"User ID: \(self.userId)" +
"Title: \(self.title)\n" +
"Completed: \(self.completed)\n"
}
}

The id is optional because when we create a new Todo object within the app it wont have an ID
until its been saved to the server.
Well be using our router to handle creating the URL requests. It assembles the requests including
the HTTP method and the URL, plus any parameters or headers. We dont need to make any changes
since the router still works in terms of URL requests and JSON. It doesnt need to know anything
about our Todo objects.
When setting up API calls you can work top down or bottom up. Either way works fine but I usually
prefer to work from the top down: starting with the high level code and working down to the lower
level details. There are several ways to implement the low level code. Working from the top down
lets us design how well interact with the lower levels. Then we can implement the bottom level to
fit the easy to understand way that weve implemented the high level code.
So well start by implementing how wed like to have the calls look in our view controller. Then
well dig in deeper to figure out how to make them work.
First, we want to be able to GET a todo from an ID number. We can do this in the View Controllers
viewWillAppear function. Remove any test code you had in viewDidLoad earlier then add a new call
to fetch the first todo in viewWillAppear. Well add a simple completion handler that checks that
the call succeeded by checking for errors or not getting a todo:

override func viewWillAppear(_ animated: Bool) {


super.viewWillAppear(animated)

// MARK: Get Todo #1


Todo.todoByID(id: 1) { result in
if let error = result.error {
// got an error in getting the data, need to handle it
print("error calling POST on /todos/")
print(error)
return
}
guard let todo = result.value else {
print("error calling POST on /todos/ - result is nil")
Swift JSON Parsing & Networking Calls 101 38

return
}
// success!
print(todo.description())
print(todo.title)
}
}

todoByID will take a completion handler. Well have to implement that functionality. Unlike previous
code that weve written, were using a completion handler in a function that weve written. Were
not just providing a completion handler to one of Apple or Alamofires functions. Well see how that
works when we implement todoByID and how well call the completion handler when we want this
function to deal with the results.
Were using a completion handler so we can make the API calls asynchronously. Notice that there
are no references to URLs or requests or JSON in the code above. It deals entirely with Todos, not
the underlying levels of abstraction.
Before digging into the implemention for todoByID, lets also figure out how well want to call other
API methods. There are probably some similarities that will mean we can share code between these
functions.
Well also want to be able to create Todos by sending them to the server. Were using the trailing
block syntax for newTodo.save so we dropped the completionHandler: label from the function call:

// MARK: Create new todo


guard let newTodo = Todo(title: "My first todo",
id: nil,
userId: 1,
completedStatus: true) else {
print("error: newTodo isn't a Todo")
return
}
newTodo.save { result in
guard result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling POST on /todos/")
print(result.error!)
return
}
guard let todo = result.value else {
print("error calling POST on /todos/. result is nil")
return
}
// success!
print(todo.description())
Swift JSON Parsing & Networking Calls 101 39

print(todo.title)
}

Weve separated creating a new Todo object locally from saving it on the server (Todo(...) vs
newTodo.save(...)). Were leaving the ID number blank on creation since that will be assigned by
the server.
Now we can start figuring out how to implement newTodo.save and Todo.todoByID.
Lets set up some Alamofire requests and see how we can interface them to those Todo calls. This
code will end up inside of our newTodo.save and Todo.todoByID functions.
First the GET request (using our handy-dandy router that creates the URL requests for us):

Alamofire.request(TodoRouter.get(1))
.responseJSON { response in
// ...
}

Now within that function we need to take the JSON that Alamofire gets for us and turn it into a Todo
object. Well have to extract each property from the JSON and set it on our new todo object. Since
well be creating a new todo object, we can write a new initializer in the Todo class to encapsulate
this conversion.
To keep our code organized, we can use an extension to hold the networking related code for the
Todo object.

Extensions add new functionality to an existing class, structure, enumeration, or


protocol type. This includes the ability to extend types for which you do not have access
to the original source code (known as retroactive modeling). - Apple Documentation
on Extensions in Swift

Create new file for this extension and name it Todo+Networking.swift. To extend the current Todo
class, start with extension Todo:

import Foundation

extension Todo {
// ...
}

Then we can add the new initializer that will create a Todo object from JSON. The JSON
representation of a Todo is a dictionary, so its type is [String: Any].
https://developer.apple.com/library/ios/documentation/Swift/Conceptual/Swift_Programming_Language/Extensions.html#//apple_ref/doc/
uid/TP40014097-CH24-ID151
Swift JSON Parsing & Networking Calls 101 40

extension Todo {
convenience init?(json: [String: Any]) {
// ...
}
}

To parse the JSON, first we need to extract each parameter from the JSON. Heres what the JSON
looks like for a single Todo:

{
"userId": 1,
"id": 1,
"title": "delectus aut autem",
"completed": false
}

To parse out each property from the JSON, use the title as a key to get it from the dictionary:

json["title"]

Then try to cast it to the type that we expect:

let title = json["title"] as? String

When parsing JSON for an initializer, we can first check that we can get all of the required properties
using a guard statement. If we dont find everything thats needed then we can return nil to indicate
that we couldnt create our object:

extension Todo {
convenience init?(json: [String: Any]) {
guard let title = json["title"] as? String,
let userId = json["userId"] as? Int,
let completed = json["completed"] as? Bool
else {
return nil
}

// ...
}
}

Since the ID property is optional, we dont need to require it in the guard statement but we do need
to parse it before creating a Todo:
Swift JSON Parsing & Networking Calls 101 41

extension Todo {
convenience init?(json: [String: Any]) {
guard let title = json["title"] as? String,
let userId = json["userId"] as? Int,
let completed = json["completed"] as? Bool
else {
return nil
}

let idValue = json["id"] as? Int

// ...
}
}

And finally we can use the existing initializer to create a Todo:

extension Todo {
convenience init?(json: [String: Any]) {
guard let title = json["title"] as? String,
let completed = json["completed"] as? Bool
let userId = json["userId"] as? Int
else {
return nil
}

let idValue = json["id"] as? Int

self.init(title: title, id: idValue, userId: userId, completedStatus: completed)


}
}

Now we can use that initializer to create our Todo.byID() function. We can move the code we had
earlier that called Alamofire.request(TodoRouter.get(id)) into this function. Since this code is
also networking related, well put it in the Todo+Networking extension:
Swift JSON Parsing & Networking Calls 101 42

import Alamofire

extension Todo {
// ..

class func todoByID(id: Int, completionHandler: @escaping (Result<Todo>) -> Void) {


Alamofire.request(TodoRouter.get(id))
.responseJSON { response in
// ...
}
}
}

The completion handler has a single argument: Result<Todo>. Alamofire defines the Result struct.
When working with Alamofire well also encounter the Response struct. Its a handy way to pack up
a bunch of bits where we used to have to use a big tuple like (URLRequest?, NSHTTPURLResponse?,
Result<Todo>). The Result struct packs up the result (our Todo object and/or an error).
Think of the Response and Result structs as little packages of data that make up what we get from
fetching the response and serializing it into the format that we want. Its kind of like when you buy
something in person. You hand over your payment and you get back a few things: your purchase,
your change, a receipt, or maybe an error message like card declined or youre short 12 cents.
All of these things make up the response to your purchase.
You could also think of the purchase and/or error message as the result of your transaction, whether
its a success or a failure.
Alamofires structs are similar: Result has .success and .failure cases and might have what you
asked for or an error. Response is higher level: it packs up the Result along with all of the other info
from the transaction like your original request and the raw response.
Within the .responseJSON completion handler, we get a Response object. We need to handle that
response, check for errors and return either an error or a Todo object. Instead of having two optional
parameters for our completion handler:

(Todo?, Error?) -> Void

We can use a single Result parameter where we expect the object returned to be a Todo:

(Result<Todo>) -> Void

Now lets fill in that function. Like with the custom initializer, well can check for errors using guard
statements. There are a few possible sources of errors:
Errors reported by Alamofire when it makes the request or serializes the JSON, e.g., 404 response, no
network connection, response isnt JSON. Those errors will be returned in response.result.error
if there are any.
Swift JSON Parsing & Networking Calls 101 43

guard response.result.error == nil else { ... }

The JSON is valid but its not in the format we expect: i.e., its not a dictionary:

guard let json = response.result.value as? [String: Any] else { ... }

Finally, we can have an error if we cant create a Todo object from the JSON dictionary.
If at any point we find an error, well give it to the completion handler as a .failure case and return:

completionHandler(.failure(error))
return

And if we succeed, well call the completion handler with the new Todo:

completionHandler(.success(todo))

So lets do that:

class func todoByID(id: Int, completionHandler: @escaping (Result<Todo>) -> Void) {


Alamofire.request(TodoRouter.get(id))
.responseJSON { response in
// check for errors from responseJSON
guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print("error calling GET on /todos/\(id)")
print(response.result.error!)
completionHandler(.failure(response.result.error!))
return
}

// make sure we got a JSON dictionary


guard let json = response.result.value as? [String: Any] else {
print("didn't get todo object as JSON from API")
completionHandler(.failure(BackendError.objectSerialization(reason:
"Did not get JSON dictionary in response")))
return
}

// turn JSON in to Todo object


guard let todo = Todo(json: json) else {
completionHandler(.failure(BackendError.objectSerialization(reason:
"Could not create Todo object from JSON")))
Swift JSON Parsing & Networking Calls 101 44

return
}
completionHandler(.success(todo))
}
}

To be able to use that BackendError type we need to declare it:

enum BackendError: Error {


case objectSerialization(reason: String)
}

And thats it for the GET call. We can run our nice pretty Todo.todoByID(id:1) call now.
But, of course, there are always more requirements. We said wed implement the POST call to save
new Todos too.
In our Todo class, well need a method to turn a Todo into a dictionary with String keys (which well
call json for convenience). Well put it in our Todo+Networking extension:

func toJSON() -> [String: Any] {


var json = [String: Any]()
json["title"] = title
if let id = id {
json["id"] = id
}
json["userId"] = userId
json["completed"] = completed
return json
}

For most of the properties, we can just add them to the String: Any dictionary:

json["title"] = title

Since the id property is optional, we need to make sure it has a value before adding it to the JSON:

if let id = id {
json["id"] = id
}

To finish implementing the save() function for Todos:


Swift JSON Parsing & Networking Calls 101 45

// POST / Create
func save(completionHandler: @escaping (Result<Todo>) -> Void) {
let fields = self.toJSON()
Alamofire.request(TodoRouter.create(fields))
.responseJSON { response in
// handle response
}
}

To handle that response, we want to do exactly the same as we did for creating a new Todo: check for
errors and parse the Todo from the JSON. So lets refactor that functionality into its own function:

private class func todoFromResponse(response: DataResponse<Any>) -> Result<Todo> {


guard response.result.error == nil else {
// got an error in getting the data, need to handle it
print(response.result.error!)
return .failure(response.result.error!)
}

// make sure we got JSON and it's a dictionary


guard let json = response.result.value as? [String: Any] else {
print("didn't get todo object as JSON from API")
return .failure(BackendError.objectSerialization(reason:
"Did not get JSON dictionary in response"))
}

// turn JSON in to Todo object


guard let todo = Todo(json: json) else {
return .failure(BackendError.objectSerialization(reason:
"Could not create Todo object from JSON"))
}
return .success(todo)
}

Since it doesnt have to be asynchronous, we can just return the Result<Todo> instead of having
to call the completion handler. We just return the .success or .failure case and let the calling
function pass it to the completion handler:
Swift JSON Parsing & Networking Calls 101 46

class func todoByID(id: Int, completionHandler: @escaping (Result<Todo>) -> Void) {


Alamofire.request(TodoRouter.get(id))
.responseJSON { response in
let result = Todo.todoFromResponse(response: response)
completionHandler(result)
}
}

And we can reuse this function in the save function:

func save(completionHandler: @escaping (Result<Todo>) -> Void) {


let fields = self.toJSON()
Alamofire.request(TodoRouter.create(fields))
.responseJSON { response in
let result = Todo.todoFromResponse(response: response)
completionHandler(result)
}
}

3.5 And Thats All


And thats it! Now we can run our nice pretty calls to retrieve and save todos that we set up at
the start. Even better, the caller of those functions no longer has any knowledge of how the todos
are getting retrieved & saved. We could completely remove Alamofire to switch to RESTKit or a
completely different implementation for the API calls without having to touch the view controller
code at all.
Heres the demo code for strongly typed API calls with an Alamofire Router.

Dont worry if you have more complex JSON to parse. Start with some simple String,
numeric and boolean fields for now. Later well do some more complex JSON parsing like
handling arrays and dates. Set up one or two more simple API calls with JSON parsing like
in this chapter.

In the following chapters well build out our gists app using Alamofire. Well set up the API calls
that we need and tie the results into our user interface. Our UI will include a table view, transitions
to detail views for individual gists, a form to create new gists, pull to refresh and swipe to delete.
Then well discuss what we can do if we dont have an internet connection.

https://github.com/RestKit/RestKit
http://en.wikipedia.org/wiki/IP_over_Avian_Carriers
https://github.com/cmoulton/https-github.com-cmoulton-grokRouterAndStrongTypesSwift3
A Brief Introduction to CocoaPods
If youre not familiar with CocoaPods, its worth taking a few minutes to learn about the lovely
dependency manager commonly used for iOS libraries today.
Cocoapods is great for adding libraries to your iOS projects, in Objective-C and Swift. In fact, its
easy to use Objective-C code in iOS Swift projects. If youre curious, check out Objective-C in Swift
Project.
Well just cover the simple basics that well use throughout this book so that when I say add the
Alamofire v4.0 CocoaPod to your project we dont need to spend a few paragraphs detailing how
to do that.

Adding a CocoaPod to a Project


Lets say were going to add the Alamofire CocoaPod to an Xcode project. Heres what we need to
do:
Close Xcode
Open the terminal in the project top directory (the directory with the .xcodeproj file for your project).
If you havent previously installed CocoaPods run:

sudo gem install cocoapods

If you need more info on installing CocoaPods, check out their Getting Started guide.
Once its done installing CocoaPods, you need to initialize Cocoapods for your project. So run:

pod init

That will create a new file called Podfile (to be honest, I think thats all it does). Using a text editor
open the newly created Podfile and see whats in there:

https://grokswift.com/objective-c-in-swift/
https://guides.cocoapods.org/using/getting-started.html

47
A Brief Introduction to CocoaPods 48

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'GrokCocoapods' do
# Comment the next line if you're not using Swift and
# don't want to use dynamic frameworks
use_frameworks!

# Pods for GrokCocoapods

end

To add pods, put them under this line: # Pods for GrokCocoapods. The last part of that line is the
name of the target in your project that you want to add the pods to, so it might be different for you.
The simplest way to add a pod is to just list it in that section in single-quotes with pod before it:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'GrokCocoapods' do
# Comment the next line if you're not using Swift and
# don't want to use dynamic frameworks
use_frameworks!

# Pods for GrokCocoapods


pod 'Alamofire'
end

Add that line to your Podfile and save it. Then switch back to Terminal and run:

pod install

That operation will download the pod and set up your project to use it. Itll create an .xcworkspace
that contains your original project and any pods youve installed. Always use the .xcworkspace
instead of the .xcodeproj file now.
Open the .xcworkspace file in Xcode. Navigate back to whatever class you want to use Alamofire
in and add import Alamofire at the top like this:
A Brief Introduction to CocoaPods 49

import Foundation
import Alamofire

class MyClass {
...
}

Now you can use Alamofire in that file.

What Does the Podfile Mean?


While its nice to just get instructions that work, its usually a good idea to know why they work.
So lets take a look at that Podfile:

# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'

target 'GrokCocoapods' do
# Comment the next line if you're not using Swift and
# don't want to use dynamic frameworks
use_frameworks!

# Pods for GrokCocoapods


pod 'Alamofire'
end

When you run pod install CocoaPods looks for a Podfile and tries to install the pods listed in it.
Install in this case means download and add to the Xcode project in the current directory. A pod
is generally a library, really just a chunk of code that you want to use in your project.
Lets go through it line by line:

platform :ios, '9.0'

This line specifies that were working on an app for iOS (not OS X) and were building an app for
the iOS 9.0 SDK. Including this info in a Podfile means that pods can have different version for iOS
and OS X as well as for different versions of the iOS SDK. Its commented out by default but you
can un-comment and update it to make sure Cocoapods pulls down the right version of the pod for
your app. For example, Alamofire 4.0 requires at least iOS 9.0. So if you specified platform :ios,
'8.0' then youd get an older version of Alamofire that does support iOS 8.0.
A Brief Introduction to CocoaPods 50

target 'GrokCocoapods' do

Projects can have multiple targets so we need to specify which target were adding the pods to.
Examples of additional targets might be tests or a today extension.

use_frameworks!

use_frameworks! tells CocoaPods how we want to integrate the code libraries with our project. In
Swift we want it to wrap up the code in a framework then include the framework in our project.
Since CocoaPods pre-dates Swift, thats not the default so we have to include this line. Want more
details? See the release notes for CocoaPods v0.36.

pod 'Alamofire'

And finally we specify which pod (or pods) we want to install.

Dependencies
The real time saver in CocoaPods is that pods can specify dependencies. So if Alamofire required
some other library then CocoaPods would make sure we have it in our Pods before downloading
Alamofire and adding it to our project. Itll also make sure that we have the correct compatible
version. So we dont need to hunt down and install a bunch of prerequisites before installing a pod.

CocoaPods Version Numbers


One option that well use in CocoaPods is to specify which version of a pod we want. We can specify
an exact number or a less specific number. For example to use v4.0.1:

pod 'Alamofire', '4.0.1'

We could also say that we want to use Alamofire v4.0.whatever to get small updates:

pod 'Alamofire', '~> 4.0.0'

Which would allow v4.0.0, 4.0.1, 4.0.2, but not v4.1.


Or even v4.whatever:
https://developer.apple.com/library/content/documentation/General/Conceptual/ExtensibilityPG/ExtensionCreation.html
http://blog.cocoapods.org/CocoaPods-0.36/
A Brief Introduction to CocoaPods 51

pod 'Alamofire', '~> 4.0'

Which would allow v4.0, 4.1, but not v5.0.


If we leave off the version number then CocoaPods will just install the latest version.

Updating CocoaPods
Unless you tell it to, CocoaPods wont auto-update to newer versions. To tell CocoaPods that you
do want newer versions (if your version numbers will allow it) run:

pod update

Youll see a message in the terminal showing you which pods were updated and to which versions.
Youll also need to run that command if you change the version numbers in the Podfile or add more
pods to it.
Thanks for Reading
Thanks for reading this free sample. I hope youve learned a bit about using APIs in iOS apps and
feel confident about continuing to develop your skills. Grab the latest edition of the book so that
you can:

Hook up a REST API to a table view


Load table view cell images from an API
Handle pagination and pull to refresh
Use basic, header-based, or OAuth 2.0 authentication
Build out your app so users can add, delete, and modify objects
Handle losing the network connection

Since youve already taken the time to work through the free chapters, Im extending an exclusive
offer to you: 10% off any version of iOS Apps with REST APIs
The book was written using Swift 3.0, Alamofire 4.0, and iOS 10 (with support back to iOS 9). Youll
get PDF, EPUB, and MOBI formats.

I had to comment on how you progressed from simple NSURLSession calls to Alamofire
serializers. I really like how you explained why you would choose to use one vs. the
other and also how using some libraries can dramatically simplify the code and reduce
boiler plate. I wish I didnt have work to do so I could finish reading it all in one sitting.
- Mark Johnson, Sr. Architect Apple Products at Proscape Technologies

The Expanded Edition: Get your app built faster


Along with the book, the expanded edition includes 2 extra guides to help you get started on your
app right away and make it easier to keep adding features.
The 8 page TL;DR Edition will help guide you through the book to find the parts you need and get
your app started.
Then once youre on your way, the Adding More API Checklist will help you through all of the
necessary steps each time you find yourself integrating yet another REST API call in your Swift
app.
Get the Expanded Edition for $35.10 (usually $39)
https://leanpub.com/iosappswithrest/c/tenpercent?utm_source=drip&amp;utm_medium=email&amp;utm_content=freeChaptersSell&amp;
utm_campaign=dripFreeChapters
https://leanpub.com/iosappswithrest/c/tenpercent?utm_source=drip&amp;utm_medium=email&amp;utm_content=freeChaptersSell&amp;
utm_campaign=dripFreeChapters

52
Thanks for Reading 53

Just the Book


Get the book (and code) now for $26.10 (usually $29)

Your book made me a rockstar in my company :) I cant thank you enough! - Dave
LaPorte, Senior Developer at Volm Companies Inc

iOS Apps with REST APIs now is the guide I wish I had on my first iOS contract when I was
struggling to figure out how to get the API data showing up in the app I was building. Get the whole
book now: iOS Apps with REST APIs now - 10% off.

http://leanpub.com/iosappswithrest/c/tenpercent?utm_source=drip&amp;utm_medium=email&amp;utm_content=freeChaptersSell&amp;
utm_campaign=dripFreeChapters
https://leanpub.com/iosappswithrest/c/tenpercent?utm_source=grokSwift&utm_medium=bookSample&utm_content=linkAtEnd&utm_
campaign=bookSample#packages
https://leanpub.com/iosappswithrest/c/tenpercent?utm_source=grokSwift&utm_medium=bookSample&utm_content=linkAtEnd&utm_
campaign=bookSample#packages

You might also like