In this day and age, there is hardly a mobile application on the iOS App Store that does not interact with networking. Networking is generally accomplished by primarily using AFNetworking, using NSURLSession or NSURLConnection as its base. While frameworks like AFNetworking and NSURLSession have become quite powerful and ubiquitous, they also come with their own hidden baggage: your web service integration code becoming tightly coupled to their library.
The current networking library programming model encourages Service Integration code to write code directly using the library’s paradigms. Examples of this include: using AFNetworking’s AFHTTPSessionManager convenience methods directly or forming NSURLSessionTasks and submitting them. Now imagine that you wanted to build two or three apps that all integrated with the same set of services, but had the architectural limitation of using another networking library. You would be forced to copy-paste or rebuild your service integration logic and re-integrate with the networking layer.
The Audible iOS Team faced this very challenge in the past year, while trying to write the Audible Integration for Kindle, several internal apps, and maintain the existing production app. However, by using several basic design patterns and abstractions the team was able to create a reusable service integration framework that has powered all Audible iOS apps ever since.
Networking on iOS
The process of invoking any web service call from an iOS app follows the same basic recipe:
- Customize a NSMutableURLRequest instance.
- Wrap it in an NSURLSessionTask or NSURLConnection.
- The networking library submits the actual HTTP Request to the service (can be one or more callbacks to the client depending on redirects, auth challenges, etc.).
- The networking library invokes callbacks to return the downloaded data and server response.
- The application aggregates/parses the response to create data model objects (in RAM or Core Data) and forwards it to the business logic for further processing.
Out of these steps, steps 1 and 5 deal with the business logic of configuring correct requests to your web services and processing the response. If we can extract these two steps into a framework, and make the rest of the network library implementation pluggable, we would have something reusable and generic.
We can easily accomplishing this by using the standard Gang of Four Factory design pattern in several places to add the required abstractions:
- A factory for creating request/response processing logic pairs for each service call (as the processing of a service response is tightly coupled with the Service API that is being invoked)
- A factory for creating the correct networking library construct: NSURLSessionTask, NSURLConnection, etc. (to allow application developers the flexibility to “snap in” their networking library of choice) and a Builder for constructing NSMutableURLRequest objects.
Service Integration Framework
Introducing these abstractions results in the architecture depicted below, delivered to Application Developers as an SDK:
Each of the mentioned steps are represented by a class:
- NSMutableURLRequest creation -> ABURLRequest & ABURLRequestBuilder subclasses, ABDataRetrieverFactoryProtocol.
- Wrap the request in a NSURLSessionTask or NSURLConnection -> ABDataRetrieverProtocol/AFDataRetriever.
- The networking library submits the actual HTTP Request to the service (can be one or more callbacks to the client depending on redirects, auth challenges, etc.) -> ABDataRetrieverProtocol/AFDataRetriever.
- The networking library invokes callbacks to return the downloaded data and server response. -> ABResponseProcessor.
- The application aggregates/parses the response to create data model objects (in RAM or Core Data) and forwards it to the business logic for further processing. -> ABResponseProcessor.
The framework expects the Application Developer to implement the ‘‘ABNetworkDataRetrieverFactory’’ and ‘‘ABDataRetrieverProtocol’’ to integrate with their application’s networking strategy and implementation of choice.
The data retriever is a representation of a service request that knows how to start, restart, pause, cancel and report progress/speed itself. Under the covers, a data retriever is an object that encapsulates a NSMutableURLRequest and a response processor. An augmented version of AFNetworking’s Response Serializers, the response processor combines parsing service responses with data model object creation and error handler. All low level boilerplate logic is pushed down into the response processor to allow the application to simply care about the high level meaning of the response: “was I logged in, with my credentials stored in the keychain? did I get all of the reviews for this book? were there more reviews?, etc..” The combination of request creation and response processor fulfill steps 1 and 5 listed above.
The data retriever protocol is implemented by each application using their network library of choice; whether it be AFNetworking, ASIHTTPRequest, or a proprietary networking library. This implementation can choose between NSURLConnection or NSURLSession and even support for short request-response vs long download/upload network interactions at this level.
The ABURLRequest & ABRequestBuilderProtocol classes are used to construct NSMutableURLRequest objects for a particular service call. ABURLRequest extends from NSMutableURLRequest and adds support for all of the basic HTTP Headers and parameters that all requests would need such as: Content-Length, Accept-Encoding, Accept, Accept-Language to name a few.
The ABRequestBuilderProtocol constructs ABURLRequest objects by exposing service call parameters pertinent to the application layer. After setting all possible parameters, calling build on the builder constructs the necessary upload data, parameters, http headers, etc. as needed.
The network data retriever factory serves as the factory where “the rubber meets the road.” It creates data retriever instances using the chosen networking library and threading model independent of any service interface. The factory can use a combination of operation queues and NSURLSessions to control how many requests run concurrently. To help in multiplexing requests to the correct queue/session, ABURLRequests declare their membership in request groups. A request group is simply an enumeration of the request type; examples being: large downloads/uploads, metrics, image downloads, default, etc. By switching on the request’s group; we can multiplex to the appropriate queue.
Putting It All Together
The Application Developer simply creates a data retriever by invoking the correct service API factory method and asks the data retriever to start the network operation. Once the service responds, the returned data is parsed completely by the response processor and returned to the delegate for further processing (letting the user know their password has been reset using an Alert in this case).
Using this framework, Application Developers can write their business logic quickly without dealing with the boilerplate of integrating with services repeatedly. This framework serves as the basis for a layering architecture on which the entire iOS family of Audible apps are written.