Blog
November 28, 2018Encapsulating Network Requests Using Moya
Since the introduction of NSURLSession in iOS7, the question of needing third party networking frameworks such as Alamofire has been hotly contested. While decoupling your application from third party dependencies can be a great thing, reputable libraries can provide a lot of battle-tested functionality that can simplify your code. Moya gives us another reason to choose Alamofire.
Moya is built on top of Alamofire, which brings in new features:
- Type-safe network requests using enumerations
- Compile-time checking of API endpoint accesses
- Simplified unit testing with built-in test stub
We will be pulling forecast information from the Dark Sky API, a free and easy weather API, using Moya.
Forecast Request
Method: GET
https://api.darksky.net/forecast/[key]/[latitude],[longitude]
Comparing NSURLSession to Moya
To appreciate the benefits of Moya, let's first look at how the API can be accessed with plain NSURLSession:
class WeatherService {
let defaultSession = URLSession(configuration: .default)
let darkSkyKey = "[SecretKey]"
var dataTask: URLSessionDataTask?
typealias QueryResult = (Weather, String) -> ()
var weather: Weather = Weather()
var errorMessage = ""
func getCurrentWeather(latitude: Double, longitude: Double, completion: @escaping QueryResult) {
dataTask?.cancel()
if var urlComponents = URLComponents(string: " https://api.darksky.net/") {
urlComponents.query = "forecast/\(self.darkSkyKey)/\(latitude),\(longitude)?exclude=minutely,flags,alerts,hourly"
guard let url = urlComponents.url else { return }
var request = URLRequest(url: url)
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
dataTask = defaultSession.dataTask(with: request) { data, response, error in
defer { self.dataTask = nil }
if let error = error {
self.errorMessage += "Error: " + error.localizedDescription + "\n"
} else if let data = data,
let response = response as? HTTPURLResponse,
response.statusCode == 200 {
self.parseWeatherData(data)
DispatchQueue.main.async {
completion(self.weather, self.errorMessage)
}
}
}
dataTask?.resume()
}
}
fileprivate func parseWeatherData(_ data: Data) {
//Code to parse data object to Weather
}
}
It's fairly straightforward to do a single GET
call using NSURLSession
. However, the class and functions can become easily bloated if there are more complex calls with multiple APIs and endpoints. Without producing a custom API manager, each endpoint function would require manual configuration, parsing, and validation that is error-prone. With Moya, code complexity and errors are reduced through the use of enumerations.
There are two main components of Moya:
- Target - A Moya target is an API service. In our example, DarkSky is a target. The target describes all the endpoints available, and the information (e.g. header, request body, etc.) required for each endpoint to successfully perform the request.
- Provider - The MoyaProvider is the primary object that is instantiated with a given target type to make network calls.
To begin, an enumeration implementing the TargetType
protocol is needed.
TargetType
requires conformance of the following variables:
- baseURL
- Type:
URL
- The target's base URL to build endpoint
- Example: https://api.darksky.net
- Type:
- path
- Type:
String
- The path of each endpoint relative to the base URL.
- Example: /forecast/
- Type:
- method
- Type:
Moya.Method
- The HTTP method for each endpoint.
- Example:
.get
- Type:
- sampleData
- Type:
Data
- Provide mocked data for each endpoint for testing.
- Type:
- headers
- Type:
[String:String]?
- The HTTP headers needed for each endpoint.
- Example: ["Content-Type": "application/json"]
- Type:
- validationType
- Type:
ValidationType
- Define what HTTP codes are considered successful.
- Example: HTTP codes between
200-299
using.successCodes
- Type:
The full example of Moya's TargetType for the DarkSky API:
public enum DarkSky: TargetType {
static private let secretKey = "[SecretKey]"
case weather(latitude: Double, longitude: Double)
public var baseURL: URL {
return URL(string: "https://api.darksky.net" class="s3">)!<>
case .weather(let latitude, let longitude):
return "/forecast/\(DarkSky.secretKey)/\(latitude),\(longitude)"
public var method: Moya.Method {
public var headers: [String : String]? {
return ["Content-Type": "application/json"]
public var validationType: ValidationType {
The MoyaProvider
can now be instantiated with the given TargetType:
var provider = MoyaProvider<DarkSky>()
DarkSky API calls can now be made using the provider as shown:
struct ClientManager: NetworkClient {
var provider = MoyaProvider<DarkSky>()
func getCurrentWeather(latitude: Double, longitude: Double, completion: @escaping(Result)) {
provider.request(.weather(latitude: latitude, longitude: longitude)) { result in
let weather = try response.map(Weather.self)
completion(nil, error.localizedDescription)
completion(nil, error.localizedDescription)
Conclusion
For more information, check out the CapTemp app which uses Moya and the DarkSky API to pull weather information at CapTech campuses here.