Moya Tutorial for iOS: Getting Started
Moya is a networking library inspired by the concept of encapsulating network requests in type-safe way, typically using enumerations, that provides confidence when working with your network layer. Become a networking superhero with Moya! By Shai Mishali.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Moya Tutorial for iOS: Getting Started
30 mins
- Getting Started
- Moya: What Is It?
- What Is Moya?
- How Is Moya Related to Alamofire?
- Moya’s Building Blocks
- Marvel API – The API of Heroes
- Creating Your First Moya Target
- Authorizing Requests in Marvel’s API
- Using Your Target
- Imgur – Sharing With Friends!
- Creating the Imgur Target
- Wrapping Up CardViewController
- Taking Moya to the Next Level
- Where to Go From Here?
Authorizing Requests in Marvel’s API
The Marvel API uses a custom authorization scheme where you create a “hash” from a unique identifier (such as a timestamp), the private key and the public key, all concatenated together and hashed using MD5. You can read the full specification in the API reference under Authentication for Server-Side Applications.
In Marvel.swift, replace task with the following:
public var task: Task {
let ts = "\(Date().timeIntervalSince1970)"
// 1
let hash = (ts + Marvel.privateKey + Marvel.publicKey).md5
// 2
let authParams = ["apikey": Marvel.publicKey, "ts": ts, "hash": hash]
switch self {
case .comics:
// 3
return .requestParameters(
parameters: [
"format": "comic",
"formatType": "comic",
"orderBy": "-onsaleDate",
"dateDescriptor": "lastWeek",
"limit": 50] + authParams,
encoding: URLEncoding.default)
}
}
Your task is ready! Here’s what that does:
- You create the required hash, as mentioned earlier, by concatenating your random timestamp, the private key and the public key, then hashing the entire string as MD5. You’re using an
md5helper property found in Helpers/String+MD5.swift. - The
authParamsdictionary contains the required authorization parameters:apikey,tsandhash, which contain the public key, timestamp and hash, respectively. - Instead of the
.requestPlaintask you had earlier, you switch to using a.requestParameterstask type, which handles HTTP requests with parameters. You provide the task with several parameters indicating that you want up to 50 comics from a given week sorted by latestonsaleDate. You add theauthParamsyou created earlier to the parameters dictionary so that they’re sent along with the rest of the request parameters.
At this point, your new Marvel target is ready to go! Next, you’re going to update ComicsViewController to use it.
Using Your Target
Go to ComicsViewController.swift and add the following at the beginning of your view controller class:
let provider = MoyaProvider<Marvel>()
As mentioned earlier, the main class you’ll use to interact with your Moya targets is MoyaProvider, so you start by creating an instance of MoyaProvider that uses your new Marvel target.
Next, inside your viewDidLoad(), replace:
state = .error
With:
// 1
state = .loading
// 2
provider.request(.comics) { [weak self] result in
guard let self = self else { return }
// 3
switch result {
case .success(let response):
do {
// 4
print(try response.mapJSON())
} catch {
self.state = .error
}
case .failure:
// 5
self.state = .error
}
}
The new code does the following:
- First, you set the view’s state to
.loading. - Use the provider to perform a request on the
.comicsendpoint. Notice that this is entirely type-safe, since.comicsis anenumcase. So, there’s no worry of mis-typing the wrong option; along with the added value of getting auto-completed cases for every endpoint of your target. - The closure provides a
resultwhich can be either.success(Moya.Response)or.failure(Error). - If the request succeeds, you use Moya’s
mapJSONmethod to map the successful response to a JSON object and then print it to the console. If the conversion throws an exception, you change the view’s state to.error. - If the returned
resultis a.failure, you set the view’s state to.erroras well.
Build and run the app. The Xcode debug console should show something similar to the following:
{
attributionHTML = "<a href=\"http://marvel.com\">Data provided by Marvel. \U00a9 2018 MARVEL</a>";
attributionText = "Data provided by Marvel. \U00a9 2018 MARVEL";
code = 200;
copyright = "\U00a9 2018 MARVEL";
data = {
count = 19;
limit = 50;
offset = 0;
results = (
{comic object},
{comic object},
{comic object},
...
)
}
Awesome work, you’ve got a valid JSON response from the backend using Moya and your new Marvel target!

The last step to complete this view controller is actually mapping the JSON response into proper Data Models — in your case, a pre-configured Comic struct.
This is the perfect time to use a different Moya response mapper that maps a response on to a Decodable instead of raw JSON.
You might’ve noticed the JSON response’s structure looks something like:
data ->
results ->
[ Array of Comics ]
Meaning two levels of nesting (data, results) before getting to the objects themselves. The starter project already includes the proper Decodable object that takes care of decoding this.
Replace the following:
print(try response.mapJSON())
With:
self.state = .ready(try response.map(MarvelResponse<Comic>.self).data.results)
Instead of mapping the object to a raw JSON response, you use a mapper that takes the MarvelResponse generic Decodable with a Comic struct. This will take care of parsing the two levels of nesting as well, which lets you access the array of comics by accessing data.results.
You set the view’s state to .ready with its associated value being the array of Comic objects returned from the Decodable mapping.
Build and run the project. You should see your first screen fully functional!

On to the detail view then!
When you tap on a comic, the starter project already has the code for showing a CardViewController and passing it the selected Comic to it. But, you might notice that tapping a comics only shows an empty card without any comic details. Let’s take care of that!
Switch to CardViewController.swift and find the layoutCard(comic:) method. Inside the method, add:
// 1
lblTitle.text = comic.title
lblDesc.text = comic.description ?? "Not available"
// 2
if comic.characters.items.isEmpty {
lblChars.text = "No characters"
} else {
lblChars.text = comic.characters.items
.map { $0.name }
.joined(separator: ", ")
}
// 3
lblDate.text = dateFormatter.string(from: comic.onsaleDate)
// 4
image.kf.setImage(with: comic.thumbnail.url)
This code updates the screen with information from the provided Comic struct by:
- Setting the comic’s title and the comic’s description.
- Setting the list of characters for the comic, or, “No characters” if there are no characters.
- Setting the “on sale” date of the comic, using a pre-configured
DateFormatter. - Loading the comic’s image using Kingfisher — a great third-party library for loading web images.
Build and run your app, and tap one of the comics in the list — you should see a beautiful information card:

You have two more features to add: uploading your card to Imgur and letting the user delete the card.