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
md5
helper property found in Helpers/String+MD5.swift. - The
authParams
dictionary contains the required authorization parameters:apikey
,ts
andhash
, which contain the public key, timestamp and hash, respectively. - Instead of the
.requestPlain
task you had earlier, you switch to using a.requestParameters
task 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 theauthParams
you 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
.comics
endpoint. Notice that this is entirely type-safe, since.comics
is anenum
case. 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
result
which can be either.success(Moya.Response)
or.failure(Error)
. - If the request succeeds, you use Moya’s
mapJSON
method 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
result
is a.failure
, you set the view’s state to.error
as 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.