The above class is a simple set of properties that provides us with a central location to store settings that will be used to configure our requests. It uses Custom Flags to dynamically change the API host and API version string depending on what Xcode config we are running the app in - it's fairly common to have staging/debug server that we develop against and a production server that our end users will use. The above properties are set using lazy loading - this could also have been achieved by directly setting the property in a custom init method (I just personally prefer lazy loading as I feel it groups the code into smaller units of functionality).

Ok configuration over, let's get down to some requesting.

iOS comes with a NSMutableURLRequest class that will form the basis of our custom request classes. Before we actually build any specific requests we need to first extend NSMutableURLRequest by adding a few convenience properties.

In the above code snippet, we create a string based enum to hold the HTTP request methods that we are going to support. The same could be achieved by using other means but by declaring an enum, it more explicitly indicates to any developers that come after you that this where the HTTP request methods should be stored and that these are the options supported.

It's common with networking calls to be parsing dates value from one format to another so here we create a generic date formatter that we will use with any request. In a real world project you will need to discuss this with the API/Server team as to what that date format should be but once chosen, it's good practice to insist that that format is the only one used - this will reduce the amount of work that you as an app developer have to do and make your project a little easier to understand.

NSMutableURLRequest comes with a property for setting the body of a request - HTTPBody. HTTPBody is of type NSData so in the above code snippet we create a new property - parameters. parameters is a dictionary that once set we then convert into an NSData instance using NSJSONSerialization and set as the value of HTTPBody, this allows our subclasses of JSONURLRequest to work at a higher level of abstraction. Please note, when debugging it can be useful to replace NSJSONWritingOptions(rawValue: 0) with NSJSONWritingOptions.PrettyPrinted.

NSMutableURLRequest comes with a property for setting the URL that your request will be made against - URL. Much like the reasoning behind the parameters property we want to allow the specific request to work at the highest level of abstraction possible, so with endpoint we allow the request to only care about the unique part of their requests e.g. users/me/profile. When this endpoint value is set we can then construct the full url by combining with the APIHost and APIVersion values declared in the RequestConfig class. This has the added benefit of allowing us to change the host or version of each request without actually having to manually edit each request source file.

This init method makes me a little uncomfortable as we need to call the designated initializer of NSMutableURLRequest which takes an NSURL value that we don't yet know so instead we fake it 😷. We could have altered this class's interface to accept more parameters in it's init signature to avoid this faking however I decided against this as I wanted the requests to only care about the properties that their request needed and not have to pass in a number of nils to an init method that is doing too much. The other interesting part is that the init method takes a defaulted RequestConfig instance as a parameter - this parameter isn't 100% necessary but having it here is really powerful when it comes to writing our unit tests and makes our class's dependency on it more obvious.

With the above class I actually toyed with making it directly as child of NSObject and then implementing a method that takes this NSObject subclass and gives us back a mapped NSURLRequest instance. However that required an additional step for the developer when creating the request and my aim with this solution was to remove the number of steps required by the developer. Trade offs like this happen all the time in development and it's important to have a metric (in this case - developer ease) to help determine what path to go down.

Making your request

Ok so we have seen our generic/base class, let's look at a specific class that actually constructs a request. In the below examples, I've created a suite of requests on a fictional users endpoint and attempted to show user cases for all the HTTP requests defined in HTTPRequestMethod enum.

The above method is very similar to the previous one but constructs a POST request rather than a GET request. It takes an emailAddress parameter that we place into a dictionary to be set as the HTTPBody of this request.

It's important to note, that a key part of this solution is that it is only the request methods should know how to transform our model classes/properties into the format and keys expected by the API. This is why in the updateProfileRequest method we pass in the parameters required as individual parameters rather than a pre-built dictionary object.

The above APIManager pattern used to isolate our networking layer is explored in more detail in a previous post. I've included it here only to show how by using our custom request instances has simplified our networking code.

In the above code snippet we have the three tests that are required for testing the forgottenPasswordEmailToBeSentRequest request are:

HTTPMethod

Endpoint

Parameters

The HTTPMethod and Endpoint tests are the same as the tests for retrieveProfileRequest with the Parameters test being slightly more complex. In the Parameters test we are checking that each individual value and key combination in the parameters dictionary is correctly set. We could have split this out into individual test for each key but i grouped them to together as I felt that these value and key combinations were one unit of work and should pass/fail together.

This pattern of required unit tests is repeated for all requests. which means that we end up with a very simple way to construct suite of unit tests that ensure a critical part of our infrastructure behaves exactly how we expect it to without having too think too intensely about it.

Finishing up 🏁

With this approach, we can create very simple to understand requests that can then be 100% unit tested and encapsulate any API specific keys from the other parts of the our project.