How To Roll Your Own Alamofire Style API Library

Date Published:
Last Modified: by

Introduction

Developing mobile applications often require interfacing the application to services located on the Internet. Most people starting out will tend to go with the solution that offers the most bang for the buck.

An example of this is using Alamofile. Alamofire is a wonderful solution for supporting a Web based API. External dependencies can help, but often bring along problems of there own. From code bloat, to potential conflicts, or even forcing changes or updates to your application at awkward moments in time.

For a simple web api, an external dependency like Alamofire can be overkill. On iOS, NSURLSession offers a fully featured and robust solution for implementing a web API client. Alamofire itself is built on top of this API. With a little bit of work you can provide a client that is “like” Alamofire enough to give you most of the benefit while avoiding that external dependency.

This tutorial will show how to implement a simple client for the Google URL Shortener service. The result will show that with a little bit of work, you can achieve a solution that has most of the benefits of Alamofire without being dependent on Alamofire.

The Application

The example application for this tutorial is essentially a single view application with text fields for inputing the URLs to shorten or lookup. There are matching buttons to invoke the Google URL shortener API.

The example code is located on GitHub.

Google URL Shortener Service

Google offers a URL shortener service. This service allows you to take a URL that is to long for use on a service like twitter, and encode it down to a small url that is used to instead. What happens is that you make a request to shorten a URL, google creates a database entry and returns a short URL. Then when the short URL is used, the Google service looks up the long URL and performs a redirect to that long URL.

To encode a URL, Google requires that the API client authenticate via two methods. The first method is by using OAuth. This is not what we will do for this tutorial. Instead we will take advantage of the second method, which is by using a API key. You will need to request your own API key via Google API Manager. This is an exercise left to the reader.

To access the API a http request is made to the URL Shortener API endpoint: https://www.googleapis.com/urlshortener/v1. The API key is included in the http request by encoding it as a query parameter. The service support three operations: insert, query, and list. This tutorial will use the insert and lookup operations. I recommend getting familiar with the API by reviewing the documentation at Google Developers URL Shortener site.

The insert operation is used to encode a long URL. It requires the request to be made with a http POST operation. The long URL is encoded as JSON and sent as the document/data portion of the request.

The lookup operation is used to return the long url for a given short url. It requires the request to be made with a http GET request. The short URL is sent as an additional query parameter.

Both the insert and get operations return a JSON document that contains both the short and long URLs.

As you can see this is a pretty simple API. While this is easily accomplished using Alamofire. You could describe that solution as “playing horseshoes with hand grenades.”

Google API Key

The first thing that should be done is to acquire the Google API key to use for this application. That involves logging onto Google’s Developer Console, creating a new project, the URL Shortener API for this project, and finally creating the credentials for the iOS application. The end result if a key that consists of a random sequence of characters. The key is used to access the URL shortener service by encoding it, as a query parameter, into the URL used for the http request.

This key is specific to your application. Google will charge for the number of operations performed with this key, currently over a million operations per day. So it will not cost anything as far as this tutorial goes. But note that a stolen key release into the wild can result to charges against your Google developer account. Hence, API keys are precious, and should be protected as much as possible.

It is a good practice not to include API keys in source code, especially when that code is checked into a public repository. Like in the case of the source code for this tutorial. So how is this done? In our case the project requires a plist file (called Secrets.plist) that contains the API key. This file is not included in in the repository by a excluding the file using the projects .gitignore file.

So to see the application running you need to create this file and populate the dictionary with a key of “GoogleAPIKey” and a String value of your key.

struct Secrets {
  private let filename: String!

  /// This property contains the API key to use for accessing a Google API
  let googleAPIKey: String?

  init(filename: String) {
    self.filename = filename

    // Load the dictionary from the secrets file
    guard let filePath = NSBundle.mainBundle().pathForResource(filename, ofType: "plist"),
        let plist = NSDictionary(contentsOfFile: filePath) else {
      print("There appears to be no Secrets file, or the Secrets file does not contain a dictionary")
      fatalError()
    }

    // Decode the API Key
    if let apiKey = plist["GoogleAPIKey"] as? String where !apiKey.isEmpty {
      self.googleAPIKey = apiKey
    } else {
      self.googleAPIKey = nil
    }
  }
}


/// Global Secrets Structure
let sharedSecrets = {
  let s = Secrets(filename: "Secrets")

  if s.googleAPIKey == nil {
    fatalError("A Google API Key is required!")
  }

  // Initialize what needs to be initialized, like the Google API Key
  GoogleURLShortenerRouter.apiKey = s.googleAPIKey!
}

The key is accessed in the Secrets.swift file shown above. The structure’s init method opens the plist file specified in it’s parameters. The init method then extracts the key from the property list and populates its property. The API key is then referenced and used by our solution to authorize the URL shortener requests.

Typical Alamofire Example

Alamofire offers a way to abstract the lower level http requests to a router object. A typical example is as follows:

Alamofire.request(Router.Search(query: "foo bar", page: 1)) // ?q=foo%20bar&offset=50

The first parameter is a object that supports the URLRequestConvertible protocol. This protocol defines a property that returns a NSMutableURLRequest. This protocol is typically implemented as part of a enum that contains the case statements that map directly to the API operations you will want to do in your application.

With this, the specifics of the API code are located in the enum, while Alamofire takes care of the lower level NSURLSession calls and handling, allowing your application concentrates on what makes it useful.

Our Own Solution

With a little bit of work we can duplicate this effect. We will develop a low level API that takes care of the NSURLSession responsibilities. A middle “router” layer that handles the URL shortener API details. And then the application that takes advantage of the router.

WebAPI Methods

The low level component is called WebAPI. The source code is grouped in Xcode under the “WebAPI” group. It is contained in several files listed below

/// enum defining WebAPI errors
enum WebAPIErrors {
  case BadRequest
  case BadStatusCode(Int)
  case NoData
  case NoResponse
  case NoRequest
}

WebAPIError.swift contains a enum that list the potential errors that the WebAPI will return.

/// enum that specifies the supported HTTP operations that WebAPI offers support
/// for.
enum WebAPIOperations {
  case Get
  case Post(data: NSData)
}

WebAPIOperations.swift contains the enum that defines the http requests it supports. We need to only implement a Get and a Post operation to support the portion of the URL Shortener API we are going to use.

The Post operation uses associated data to pass the data component for the http request that the WebAPI will perform.

/// Protocol used for a WebAPI router component
protocol WebAPIRouter {

  /// Property that returns a NSMutableURLRequest configured for the
  /// operation that is going to be performed.  It is called by the
  /// WebAPI.request function
  var request: NSMutableURLRequest? { get }

  /// Property that returns the WebAPIOperations value that need to be
  /// performed.  It is called by the WebAPI.request function
  var method: WebAPIOperations { get }
}

WebAPIRouter.swift contains the protocol definition that is used to pass request information from the custom router object to the WebAPI methods.

The request property is accessed when the WebAPI methods need a NSMutableURLRequest. It is the responsibility of the router object to properly configure the request for the operation that will be performed. This would include the proper URL to used along with setting any header parameters.

The method property returns the WebAPI operation that needs to be performs (Get or Post).

That is all the information that is required for the request to be made.

/// used for returning results from WebAPI calls
enum WebAPIResult<A, Error> {
  case Success(A)
  case Failure(Error)
}

Results are passed back from the WebAPI methods by using the WebAPIResult enum defined in the WebAPIResults.swift file. It is a generic that on success returns the NSData component of the http response. On failure it returns the WebAPIError.

/// WebAPI structure.  Mostly a placeholder for the static methods that
/// that the API provides.
struct WebAPI {

  /// Method that invokes the required http operation.
  ///
  /// - parameter router: A WebAPIRouter object
  /// - parameter handler: A block that is called when the operation is
  /// complete.
  static func request(router: WebAPIRouter, handler: (WebAPIResult<NSData, WebAPIErrors>) -> Void) {
    if let request = router.request {
      var task: NSURLSessionTask?

      switch router.method {
      case .Get:
        task = NSURLSession.sharedSession().dataTaskWithRequest(request) { (data, response, error) in
          handler(parseResponse(data, response: response, error: error))
        }
      case let .Post(data):
        task = NSURLSession.sharedSession().uploadTaskWithRequest(request, fromData: data) { (data, response, error) in
          handler(parseResponse(data, response: response, error: error))
        }
      }

      task?.resume()
    } else {
      handler(WebAPIResult.Failure(.NoRequest))
    }
  }

  /// Method that converts NSData to a JSON dictionary
  ///
  /// - parameter data: The NSData that needs to be converted
  /// - returns: A [String: AnyObject] on success that contains the JSON
  /// data.  Else nil if there was an error
  static func parseJSON(data: NSData) -> [String: AnyObject]? {
    var json: [String: AnyObject]?

    do {
      json = try NSJSONSerialization.JSONObjectWithData(data, options: .AllowFragments) as? [String: AnyObject]
    } catch _ {}

    return json
  }

  /// A private method used to parse the http response received.
  ///
  /// - parameter data: optional NSData parameter
  /// - parameter response: optional NSURLResponse
  /// - parameter error: optional NSError
  ///
  /// - returns: WebAPIResult<NSData, WebAPIErrors> that contains the decoded
  /// results of the operation
  private static func parseResponse(data: NSData?, response: NSURLResponse?, error: NSError?) -> WebAPIResult<NSData, WebAPIErrors> {
    var r: WebAPIResult<NSData, WebAPIErrors>?

    if error != nil {
      r = WebAPIResult.Failure(.BadRequest)
    } else if let rsp = response as? NSHTTPURLResponse where 200...299 ~= rsp.statusCode {
      if let d = data {
        r = WebAPIResult.Success(d)
      } else {
        r = WebAPIResult.Failure(.NoData)
      }
    } else if let rsp = response as? NSHTTPURLResponse {
      r = WebAPIResult.Failure(.BadStatusCode(rsp.statusCode))
    } else {
      r = WebAPIResult.Failure(.NoResponse)
    }

    return r!
  }
}

WebAPI.swift file contains a structure that has three static methods defined. The first method is used to initiate a request operation. The first parameter is a WebAPIRouter object (like Alamofire). The second parameter is a handler block that is passed the WebAPIResult of the operation. It should be noted that the handler is not called on the applications main thread.

The second method is a utility method used to convert a NSData response to [String: AnyObject] that contains the JSON response. This dictionary optional is nil if there was a error during the conversion.

The last method is a private method used to parse the response from the http operation requested. It will convert the results into the WebAPIResults enum value. It is pretty straight forward in that a NSHTTPURLResponse needs to be returned with response code in the 200 to 299 range.

In effect the specifics to NSURLSession calls are contained at this level.

Google URL Shortener Router

A structure defined in the GoogleURL.swift file defines the data that we are interested in from the Google URL Shortener API

private let urlRegEx = "^(http(s)?://.)?(www\\.)?[-a-zA-Z0-9@:%._\\+~#=]{2,256}\\.[a-z]{2,6}\\b([-a-zA-Z0-9@:%_\\+.~#?&//=]*)$"

struct GoogleURL {
  private static let defaultURLString = "unknown"
  var longURL: String
  var shortURL: String

  /// Returns true when the contents of the shortURL property is a valid URL
  var isShortURLValid: Bool {
    return isURLValid(shortURL)
  }

  /// Returns true when the contents of the longURL property is a valid URL
  var isLongURLValid: Bool {
    return isURLValid(longURL)
  }

  init(longURL: String, shortURL: String = GoogleURL.defaultURLString) {
    self.longURL = longURL
    self.shortURL = shortURL
  }

  init(shortURL: String, longURL: String = GoogleURL.defaultURLString) {
    self.shortURL = shortURL
    self.longURL = longURL
  }

  init() {
    self.longURL = GoogleURL.defaultURLString
    self.shortURL = GoogleURL.defaultURLString
  }


  /// Private function used to test the validity of a string as a URL
  private func isURLValid(path: String) -> Bool {
    if let regex =
      try? NSRegularExpression(pattern: urlRegEx,
                   options: NSRegularExpressionOptions.CaseInsensitive) {


      let match = regex.numberOfMatchesInString(path,
                options: .ReportCompletion, range:
                NSRange(location: 0, length: path.characters.count))

      if match > 0 {
        return true
      }
    }

    return false
  }
}

It has properties for the long form and short form of a URL. It also has two properties that indicates if there is a URL defined in the two URL properties. This check is done by a regular expression. The application uses this it enable the buttons for performing the Google URL Shortener operations.

The public interface to the custom Google URL Shortener API is contained in GoogleURLShortener.swift.

/// Returns the results of a GoogleURLShortener request
enum GoogleURLShortenerResults<A, Error> {
  case Success(A)
  case FailedParse
  case Failure(Error)
}

Like the lower level WebAPI, this layers uses a enum to return the results from any API calls that are made.

typealias GUSResult = GoogleURLShortenerResults<GoogleURL,WebAPIErrors>

A type alias is used to save on typing…

struct GoogleURLShortener {

  static func requestShorten(longURL: String, handler: (GUSResult) -> Void) {

    WebAPI.request(GoogleURLShortenerRouter.Shorten(longURL: longURL)) { result in
      var r: GUSResult?

      switch result {
      case let .Failure(error):
        r = GUSResult.Failure(error)
      case let .Success(data):
        if let gu = parseJSONData(data) {
          r = GUSResult.Success(gu)
        } else {
          r = GUSResult.FailedParse
        }
      }

      callHandler(r!, handler: handler)
    }
  }


  static func requestLookup(shortURL: String, handler: (GUSResult) -> Void) {

    WebAPI.request(GoogleURLShortenerRouter.Lookup(shortURL: shortURL)) { result in
      var r: GUSResult?

      switch result {
      case let .Failure(error):
        r = GUSResult.Failure(error)
      case let .Success(data):
        if let gu = parseJSONData(data) {
          r = GUSResult.Success(gu)
        } else {
          r = GUSResult.FailedParse
        }
      }

      callHandler(r!, handler: handler)
    }
  }


  private static func callHandler(result: GUSResult, handler: (GUSResult) -> Void) {
    dispatch_async(dispatch_get_main_queue()) {
      handler(result)
    }
  }

  private static func parseJSONData(data: NSData) -> GoogleURL? {

    if let json = WebAPI.parseJSON(data) {
      if let shortURL = json["id"] as? String {
        if let longURL = json["longUrl"] as? String {
          return GoogleURL(longURL: longURL, shortURL: shortURL)
        }
      }
    }

    return nil
  }
}

The GoogleURLShortener struct contains two public methods. One method to perform the operation to generate a short URL. The second is used to lookup the long URL that is associated with a short URL.

The requestShorten method is used to generate a short URL given the long URL. The long URL is passed to the method as a String. The result is returned to the handler block that is passed in. This block will not be called on the application’s main thread.

The requestLookup method is used to find the matching long URL for a given short URL. Like the previous method the short URL is passed in as a string. Also like the shorten method, the handler will not be called on the application’s main thread.

Both methods use a private parseResponse method, which essentially decodes the WebAPIResults enum into a matching GUSResults. It in turn calls the private method for converting the returned JSON data into a GoogleURL structure. this information is returned to the high level application code.

Note that the callHandler private method is used to pass the results back to the application on the main thread by first making a call to dispatch_async.

/// WebAPIRouter for the Google URL Shortener API
enum GoogleURLShortenerRouter {
  static let basePath  = "https://www.googleapis.com/urlshortener/v1/url"
  static var apiKey: String = ""

  case Shorten(longURL: String)       // Shorten a long URL
  case Lookup(shortURL: String)       // Lookup the short version of a long URL

  /// property that gives the url
  var url: NSURL? {
    switch self {
    case .Shorten:
      return NSURL(string: GoogleURLShortenerRouter.basePath + queryString())
    case let .Lookup(url):
      return NSURL(string: GoogleURLShortenerRouter.basePath + queryString(["shortUrl": url]))
    }
  }

  /// Function that generates the query parameter portion of the URL.
  /// Always encodes the API Key into the string.  Then optionally includes
  /// any passed parameters.
  ///
  /// - parameter queryParameters: A [String: String]? dictionary of
  /// query parameters to encode into the url
  /// - returns: a url represented as a String
  func queryString(queryParameters: [String: String]? = nil) -> String {
    var qs: String = "?key=\(GoogleURLShortenerRouter.apiKey)"

    if let p = queryParameters {
      for (k, value) in p {
        if let encodedValue = value.stringByAddingPercentEncodingWithAllowedCharacters(NSCharacterSet.URLQueryAllowedCharacterSet()) {
          qs += "&\(k)=\(encodedValue)"
        }
      }
    }

    return qs
  }
}

The file GoogleURLShortenerRouter.swift contains the definition for the enum that supports the WebAPIRouter protocol. The enum defines two operations. One for performing the shorten operation, the other for performing the lookup.

The enum has two static parameters. One contains the base path to be used in generating the NSURLs to use in the http requests. The other static parameter contains the Google API Key. The key is set by the Application’s delegate by accessing the sharedSecrets method defined in the Secrets.swift file reviewed earlier.

An additional property is used to return a NSURL for performing the desired operation. The property correctly encodes the parameters according to the Google URL shortener web API. It uses the method queryString.

The queryString method will always encode the access key as the first queryParameter. The only additional query parameter is encoded for the shorten operation.

extension GoogleURLShortenerRouter: WebAPIRouter {

  /// WebAPIRouter protocol property that returns a NSMutableURLRequest
  var request: NSMutableURLRequest? {
    var request: NSMutableURLRequest?

    if let url = self.url {
      request = NSMutableURLRequest(URL: url)

      switch self {
      case .Shorten:
        request?.HTTPMethod = "POST"
        request?.addValue("application/json", forHTTPHeaderField: "Content-Type")
      case .Lookup:
        request?.HTTPMethod = "GET"
      }
    }

    return request
  }

  /// WebRouter protocol property that returns the WebAPIOperations that
  /// is required for the requested operation
  var method: WebAPIOperations {
    switch self {
    case let .Shorten(longURL):
      var data: NSData?

      do {
        data = try NSJSONSerialization.dataWithJSONObject(["longUrl": longURL], options: NSJSONWritingOptions())
      }
      catch _ {}

      return WebAPIOperations.Post(data: data!)
    case .Lookup:
      return .Get
    }
  }
}

The final part of this file contains a extension that implements to properties that are specific to the WebAPIRouter protocol. A shorten operation requires NSMutableURLRequest setup for a post operation with the expected response to return JSON information. The lookup operation needs a NSMutableURLRequest setup for a get operation.

The last property returns the WebAPIOperations enum for the Google URL Shortener request that needs to be performed. Note that the Shorten call requires the long form URL to be JSON encoded. This is done using NSJSONSerialization.

The Application Code

The application code is pretty straight forward at this point. The main view controller that two @IBAction functions that handle the requests when a particular button is pressed.

  @IBAction func shortenURLRequested(sender: AnyObject) {
    if let lu = longURLField.text {
      GoogleURLShortener.requestShorten(lu) { result in
        switch result {
        case let .Success(googleURL):
          self.googleURL = googleURL
        case .FailedParse:
          self.showGoogleURLShortenerErrorMessage("Could not parse the response from the service!")
        case let .Failure(error):
          self.showGoogleURLShortenerErrorMessage("WebAPI Error \(error)")
        }
      }
    }
  }

They check to make sure that the required text fields contain data before making the call. The information is decoded and UI components are updated the the handler is called.

  @IBAction func lookupURLRequested(sender: AnyObject) {
    if let su = shortURLField.text {
      GoogleURLShortener.requestLookup(su) { result in
        switch result {
        case let .Success(googleURL):
          self.googleURL = googleURL
        case .FailedParse:
          self.showGoogleURLShortenerErrorMessage("Could not parse the response from the service!")
        case let .Failure(error):
          self.showGoogleURLShortenerErrorMessage("WebAPI Error \(error)")
        }
      }
    }
  }

Pretty straight forward…

Conclusion

Further refinement can be made to this solution:

Hopefully this opens up the possibility of not having another external dependency for the your next application.

Rodger Higgins is the founder of Spazstik Software, LLC. He has created StackCalc, The Visual Touch Calculator and SPZTracker.

Recent Articles

A Reusable Observer Protocol Written In Swift

One design pattern that I use a lot is the observer pattern. The observer pattern is used when you have an object that needs to notify a list of objects that state changes have happened. This article discusses a reusable component, in Swift, I developed to speed up my development process.


Read More...
How To: Support User Editable Python Macros In A I Os Application

Last month I published a article on how to use JavascriptCore for extending a iOS application with macro support. While Javascript has many uses, as a way for application customization, it would not be my first choice.

A better choice to me would be a language like Python. Being curious, I wondered what it would take to to use Python. This article discusses what I found.


Read More...
How To Example: Extend A I Os Using Javascript Core As A Macro Engine

JavascriptCore is a framework that offers the ability for a iOS application to interact with javascript code. Primarily used for cross platform code sharing, it can also be used to extend a iOS application with macro capabilities. This framework offers a world of possibilities for extending any iOS application.


Read More...
How To: Custom I Os Activity Tracker View Using Ca Layers

The Apple Watch shipped with a captivating activity tracker. The center piece is a really cool spiral animation scheme showing the amount of activity during the day. This image is also shown on the matching iPhone Activity App. I have always wanted to see what it would take to implement this myself. The examples that I see typically use a custom drawRect override, but I always wanted to see what it would take to do with CAShapeLayers.

Implementing a 0-100% control is straight forward when using CAShapeLayer. But how do you implement a progress indicator that support progress values greater then 100%? This How To discusses a solution that I came up with along with it’s potential limitations.


Read More...
How To Display Custom Content On A External Screen From A I Os Device

Being able to display content on a external screen or device is a great capability to add to a iOS application. Especially how easy it is. This article will show the step required to to do this.


Read More...

Follow us on

Articles by published month

Articles by subject matter

Rails Thor Compass Susy Modernizr Rspec Capybara Bettererrors Railspanel Aws Rack Railscasts Http Aws-elastic-beanstalk Ruby-on-rails Rack-rewrite Http-response-codes Pow-amazon-route-53 Stackcalc Iphone Ios Mobile Application Skeumorphic Dns Web-site Elastic-beanstalk Elastic-ip Elastic-load-balancer Tutorial Howto Javascript Javascriptcore Macro Example Swift Design-patterns Observer Python Macros Alamofire External-screen-support Apple-watch Activity-tracker Office Status S100 Cloud Astronomy Picture Mars Apple Usb-c Leonard-nimoy William-shatner Geotag Gps Spztracker Geotagging Photos Secret Marketing Watch Watch-repair Head-transplants Perception Diabeties Sugar Health Rosette-nebula News Spock Comet-lovejoy

Click here to receive free tips and tutorials!

This web site uses javascript exclusively for automating html elements. Please enable javascript to fully experience the features offered on this site.