Getting Started with Moya
Moya is a Swift Network Abstraction Library. It provides us with an abstraction to make network calls without directly communicating with Alamofire. With this tutorial we will learn how to get started with the framework, discover some tips & tricks and learn all basics from code examples.
Why Moya?
Let’s firstly discuss why NOT Moya:
- It introduces a dependency to your project. Your project’s success is partially determined by Moya’s stability.
- There are way more reasons why you would NOT want to use Moya but they are all derived side effects of point number one. You should always consider the cost of adding a dependency to any project. For a more in-depth analysis on why it’s better to build your own API manager from scratch in Swift, please do yourself a favor and read this great article by Matteo Manferdini.
When I first started using Cocoapods I thought found some developer superpowers. I eventually realized that it is more important to understand how things are achieved rather than having super code from a library. This is how you improve your skills as a developer. Behind Moya is NOT some black magic code that communicates with servers and returns JSON!
If we allow others to do our thinking, we shall have crippled energies and contracted abilities. — Ellen G. White
Why you WOULD Moya:
- Set up an API Manager in no time.
- Clean code and best practices.
- Focus on your applications core features instead of networking.
- Easy to stub network responses for unit tests.
- Learn how to use a popular Swift networking library. Have you ever seen “Experience with REST API’s using best practice and well-known libraries” on a job post? I definitely have.
There are many benefits to using Moya these and others included, but this does not mean that writing a Network Layer in pure Swift is difficult or not desirable.
Set-by-step Guide
Installation
Moya can be installed using your preferred means of installing dependencies: https://github.com/Moya/Moya#installation
Tip: Always specify library versions when installing dependencies. For example, if you are using Cocoapods, target a specific version number for your project!
pod ‘Moya’, ‘10.0.1’ # latest version at the time of writing.
Preparation
Before you get to building your Network manager I would advise some design and planning beforehand! Yes, I know, as developers we just want to get to the code. But taking some time to plan our work would be a worthy investment.
So what should I plan to build a network manager? Not that much! Let’s keep it simple and down to two questions. I always begin my planning with questions which I need to provide the answers:
- Which endpoints will I be requesting data from?
- How will I model the data returned from the endpoints?
When answering these two questions I just simply make a list of all the endpoints in notes and create pseudocode models from the expected JSON. If you are working with an already documented API you can use the documentation provided as the reference. I will make use of The Movie Database API 🍿 for most of the examples.
Models
Thanks to Swift 4’s Codable protocol we don’t have use any object mapping libraries or write custom mappers to convert our JSON to a model. Let’s build our Movie Model, MovieResults Model.
Movie Model
If we make a request to The Movie Database API to get playing movies, the returned JSON response looks like this. From this JSON we want to model two objects MovieResults and Movie. We will make use of these two structs to model our JSON response.
Things to note:
- When the name of the variable you are using is different to the one the API provides you need to implement a CodingKey enum. It tells Swift exactly how to map your JSON.
MovieResults Model
Similar to the Movie Model we need to conform to the Decodable protocol.
Things to note:
- Here we see that Decodable can even map arrays of objects for us.
API Enum
Create an enum to house all your endPoints. Each case should take the parameters you want to pass to that specific endPoint.
Things to note:
- Create a case for each endPoint.
- When you have an endPoint that requires a lot of parameters, your case can get very long and ugly. You can keep things clean by passing a dictionary:
// First import Alamofire to make use of ‘Parameters’
case endPointWithLotsOfParams(parameters: Parameters)
Let’s get to real work!
We will look at the Target Type Protocol, building a Network Manager, Dependency Injection and Plugins.
Target Type Protocol
Now that you have created your API enum with all your endpoints listed we need to import Moya and adhere to the Target Type Protocol. Moya’s Target Type protocol consists of 7 properties: baseUrl, path, method, sampleData, task, validate, header.
Execution
Let’s make our MovieApi enum conform to the TargetType protocol.
We use enums to switch between different environments. This is a neat way of providing the functionality to switch between environment. Make sure, that this property is a static constant as you don’t want your application to switch environments on the fly. I would suggest having such configurations tied to a Xcode build configuration instead of in code.
baseUrl
The root of our URL.
The red is our baseURL followed by the version of the API. So https://api.themoviedb.org/3/ will be our baseURL. There are instances where we can have multiple environments such as QA, Staging and Production with a different baseURL. Let’s see how we can cater for both situations.
When we have one baseURL it is straightforward. Create an URL with the string to the baseURL and return a URL. URL(string: String) returns an optional URL so we unwrap the value and throw an error if we get a nil. Let’s look at a configuration that handles multiple environments.
Create a Swift file named NetworkManager. Then create an enum that will have a case for each environment we could have. Next make a struct that will have our provider and a static instance of the environment.
Things to note:
- Our Network Manager is a struct which is a value type.
- We keep our provider private as we do not want anyone outside of this file to access the provider directly.
path
The path is best described in Moya’s own code. By pressing ‘Option’ followed by a click on the path property and Xcode will present you with a popover.
method
The type of HTTP request method to use for a particular endPoint. Moya provides us with all the HTTP request methods that we would need to perform all CRUD operations.
Sometimes, all you will need to use is the GET method. In this case there is no need to switch on Self. Do not use a switch-case when not necessary. You can just return get.
When you do perform other methods a switch on self is required. Make sure that you provide the right method for each request. Sending a delete request to a getter method on the API you will be presented with beautiful errors and unexpected behavior.
Tip:
- Use Moya.Method as sometimes Xcode cannot find the Method namespace.
sampleData
For the purpose of providing data for your tests. When writing Unit Tests your application should never communicate with the API. You need to test your application in isolation. This is why you need to provide a sampleData. I personally like to keep my production code and testing code separate so I create a separate file for my sampleData and also create separate JSON files for each endPoint.
Inside the MovieAPI+Testing I will have my sampleData and a method that returns the JSON data from the JSON files in our StubbedResponses group.
Creating stubbed response from our JSON files.
Tip:
To create a JSON file in Xcode just CMD+N then select empty file and name it adding “.json” at the end.
task
Different API’s expect data to be given to them in different ways. The task property is where you handle this. Parameters in each task can be sent in various ways.
- requestPlain — Used when the request does not require any additional data. For example a simple get request.
- requestData — Used when the request expects data to be passed in the body of the request.
- requestJSONEncodable — Used when the request expects a JSON encodable object whereas no.2 expects Data.
- requestParameters — Used when you want to pass parameters and define your own type of encoding. (Look at example below)
- requestCompositeData — Used for when you want to pass Data as the request body and also pass urlParameters at the same time.
- requestCompositeParameters — Same as no.5 but takes parameters instead.
- downloadParameters — As the name suggests used to download and also pass parameters if needed.
- uploadCompositeMultipart — Used to upload multipart with parameters.
validate
This property is used by Alamofire to validate responses. It is by default set to false so responses won’t be validated. You can use it to validate whether the response you are getting matches what is expected in your headers. Validate is also used to confirm the response status codes. For more on this you can look at this 👈or even👉 that.
headers
Every request that you make via HTTP has headers. For more information on headers read this article.
Network Manager
We need to create a Networkable protocol. This protocol defines all properties and methods that you require your manager have.
Creating your NetworkManager. As the Moya documentation suggests make use of a struct for your Manager.
Tip:
- Moya has a request method that returns a progress block. Using this property plus a progress view you can easily implement a nice loader into your application.
Dependency Injection
Many times when developers write a network layer they expose it to their ViewController by using a singleton class that they can access everywhere. This was once a bad practice that even I partook in. The problem with this approach is that your code becomes tightly coupled to the Network Manager and almost untestable. Singletons are very hard to test, trust me I have tried. So how do you get your Network Manager into your ViewController? Dependency Injection! We simply create a Network Manager instance and pass it to ViewControllers that need it.
Here we create a provider and pass it to our MainViewController. The MainViewController needs a provider (it depends on the provider) so we give (inject) an instance of the network provider.
When having such an implementation you have complete control over the NetworkManager object that is injected. Making the code less coupled and much easier to test!
Things to note:
- We pass an object of type Networkable. This way we can pass in any object that implements this protocol.
- By using Networkable we can easily create mocks and pass them into this class.
Plugins
Moya comes already with some nifty plugins that you can use. I like making use of the networkLoggerPlugin which simply logs all network activity to the console as it happens.
let provider = MoyaProvider<MovieApi>(plugins: [NetworkLoggerPlugin(verbose: true)])
Change your provider instantiation so that it looks like the one above and your application’s network traffic will be logged to the console. There are other plugins that you can add to your provider. You can even write your own custom plugins. More on plugins here.
Conclusion
In summary using Moya boils down to creating an enum that conforms to the TargetType protocol. It is amazing what the combination of Swift enums and protocol oriented programming can achieve. With this implementation we have a clean well defined network layer that any new developer can read and understand at a glance.
Hope you enjoyed the read. Maybe I convinced you to try out Moya in your next project. What do you prefer using a network library or coding one yourself? 🤔 Please, share your thoughts in the comments.
Some random fun fact:
Moya happens to mean ‘Spirit’ in Zulu which is one of South Africa’s 11 official languages.
References: