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
  • path
    • Type: String
    • The path of each endpoint relative to the base URL.
    • Example: /forecast/
  • method
    • Type: Moya.Method
    • The HTTP method for each endpoint.
    • Example: .get
  • sampleData
    • Type: Data
    • Provide mocked data for each endpoint for testing.
  • headers
    • Type: [String:String]?
    • The HTTP headers needed for each endpoint.
    • Example: ["Content-Type": "application/json"]
  • validationType
    • Type: ValidationType
    • Define what HTTP codes are considered successful.
    • Example: HTTP codes between 200-299 using .successCodes

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">)!<>

}

public var path: String{

switch self {

case .weather(let latitude, let longitude):

return "/forecast/\(DarkSky.secretKey)/\(latitude),\(longitude)"

}

}

public var method: Moya.Method {

switch self {

case .weather:

return .get

}

}

public var sampleData: Data {

return Data()

}

public var task: Task {

switch self {

case .weather:

return .requestParameters(parameters: ["exclude":"minutely,flags,alerts,hourly"], encoding: URLEncoding.queryString)

}

}

public var headers: [String : String]? {

return ["Content-Type": "application/json"]

}

public var validationType: ValidationType {

return .successCodes

}

}

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

switch result {

case let .success(response):

do{

let weather = try response.map(Weather.self)

completion(weather, "")

} catch let error{

completion(nil, error.localizedDescription)

}

case let .failure(error):

completion(nil, error.localizedDescription)

}

}

}

}

Conclusion

Additional endpoints would simply require extending the enumeration in the TargetType and adding the parallel functions in the ClientManager. As the API expands with more endpoints, the benefits become much more obvious. By comparing the code between NSURLSession, the Moya-powered network layer reduces bloat and is significantly more readable, while the option to provide mock data makes testing a breeze. These benefits reduce errors and allow newer developers to ramp up quickly.

For more information, check out the CapTemp app which uses Moya and the DarkSky API to pull weather information at CapTech campuses here.