In this article, I would like to describe a development process that helps speed up the delivery of an iOS application. Organization and proper project structure are especially important while working with a large group of developers divided into multiple teams.
An application’s root components are user interface, the business logic behind, and communication with services over a network. Multiple separate teams must work together, each with their own speed and workflow. As a result, a lot of time is being wasted on synchronization among them.
Have you ever been blocked by other team members because you were waiting for certain endpoints or designs? Yes, so have I.
I would like to focus on the cooperation between mobile and backend developers. An implementation plan, an API design, and documentation are the first things every team must create at the beginning. This will work in the initial phase, but at some point, something will go wrong — the only question is when. Especially in the later stages, some changes and adjustments are going to be made that will force to redesign and reimplement things from scratch.
I will address how to resolve these problems using frameworks and mocks. In this method, a developer can switch between a mocked and real backend at a time of his choosing. The main advantage is that neither backend nor mobile team doesn’t block each other while changing the current implementation.
Using frameworks has many advantages. The most important is code separation: you are one hundred percent sure that the target contains only the necessary code and no other dependencies.
It is especially useful while separating the application from the network layer. If your application implements other independent functionalities you can extract them into submodules as well.
Another thing is shorter compilation time — code changes in one framework won’t force another to be recompiled. You can read more about using frameworks here.
Frameworks are independent of the type of project pattern you are using (MVC, MVP, MVVM, etc.) — network layer separation will work with any of them.
The below figure shows the project structure of a sample application.
project contains the main application code: a user interface, the business logic, the database, etc. It also contains unit and UI tests related to the application itself.
framework contains all classes that wrap up communication with external web services, data parsing, and validation. Unit tests are separated as well so that they won’t get mixed with application tests.
Below I described each component in detail along with the most significant code snippets.
Models are the data structures exchanged between the application and the backend — usually represented by JSON or XML. You can use whichever parser you prefer. In this example, the model conforms to the Codable protocol.
A networking class is a public interface exposed to an application. It uses a Facade pattern where only some relevant methods are accessible while its internal logic is hidden. This enforces and guarantees better code separation in contrast to a single module.
useMockedApiClient — determines which API client to use: the mocked or the backend one. You can switch between them by setting the property in only one place.
mockedRequestDelay — the time after a mocked request is returned. It simulates communication latency between the server and the application over a network. It can be set to 0 for development in order not to waste time on loading so that new features can be tested quickly. The most important thing is having this property set to a huge value e.g. 10 seconds, and then testing if the application works correctly with a poor network connection. You can test if the user interface components are blocked and spinners displayed at a proper time. This wouldn’t be easy using a real connection or other tools that simulate network latency.
configuration — described above.
apiClient — the API client that communicates with the real backend.
mockedAPIClient— the client that uses mocked data.
client — the public property exposed to the application in order to communicate with either the real or mocked backend depending on the useMockedApiClient property.
The most important component is the API client, which is responsible for the communication with the backend. The goal is to have two different implementations of the API client: real and mocked.
APIClient contains the definition of a public interface exposed to the application target.
accessTokenProvider — the access token storage.
authentication, products— the group of endpoints divided into categories.
BackendAPIClient — the client that communicates with the backend.
Endpoints are grouped into categories, in this example, there are named Authentication and Products.
MockedAPIClient — the client that uses mocks.
Returns the data created by a mocked data factory. The main advantage is that you can control data you want to test the app with. For example, you can put very long texts, different variations of attributes representing your business logic, and check if everything is displayed inside the application properly.
Endpoints are grouped into categories. For simplicity, I’m showing only one group with a user authentication method.
Internal BackendAuthentication implementation communicates with the backend, parses the response, and handles errors.
Internal MockedAuthentication implementation creates and returns mocked data. Data can be loaded and parsed from local JSON files, but I encourage them to create data structures using factory class — MockedDatabase in this example. The main advantage of this is that it is easier to maintain model changes because they are validated during the compilation time. Mocked data can be modified and stored in memory as well: the developer can manipulate it during each launch session and all the changes made in one view will be reflected in others instantly.
There is a large number of available methods on how to mock network requests. Some of them are in the form of an external library and others require to be implemented by the developer. Below I’m presenting a few alternatives you can use to stub application with mocked data.
OHHTTPStubs is a library for stubbing network requests. One of the best features in this framework is a tool that allows simulating network speed — it is especially useful to test application behavior using a slow internet connection. Another great tool allows us to record network responses and create mocks from them! You can record entire communication with the server and turn its responses into mocks. It will be very easy to update or create new mocks in case of backend changes.
OHHTTPStubs is bound to NSURLSession, so you can use it with all popular networking frameworks like Alamofire and AFNetworking. OHHTTPStubs is available using CocoaPods and Carthage. More information is available on GitHub.
Kakapo allows mocking server behaviors and responses. Its usage is similar to OHHTTPStubs. It is bound with NSURLSession, as a result, it doesn’t require changing current application implementation. All you need to do is create a router which intercepts network request and returns mocked response. The router can be created and registered dynamically based on the current user input. It allows simulating real backend where mocked responses will be updated with changes users made during the application runtime! Kakapo is available using CocoaPods and Carthage. More information is available on GitHub.
Mocking server with WireMock. Marco Santarossa in his great article describes a different approach on how to provide mocked data to an iOS application. The idea is to connect the application to a local server created using WireMock. The main benefit of this method is that neither test code nor data is embedded inside the application. Moreover, it doesn’t require to implement and maintain code responsible for delivering mocks.
Frameworks separate the functionality into submodules, which makes code more readable. They shorten compilation time and are independent of a chosen project pattern. You can implement separated unit tests as well, which keeps coding much more cleaner.
Using a mocked backend is debatable: its relevance can be established for each project individually. Nevertheless, it saves a lot of time, especially while both the application and the backend are being developed at the same time. One team doesn’t have to stop working on features in case of either adjustments, deployment, or connection errors.