Creating a Service Layer in Swift
Modularity for the win
What is a Service layer?
A service layer allows you to move logic related to frameworks and APIs into their own class or struct. A good practice is to create a protocol and add the required methods and computed properties. Your implementation will be a class or a struct that conforms to this protocol.
The sample project created for this article makes use of UIKit, the MVVM design pattern, and a touch of Apple’s Combine framework. If you’re not familiar with Combine, that’s ok. You won’t need to be a master of Combine to benefit from this article.
Creating a service layer allows you to pull out framework specific logic from your view model (MVVM) or view controller (MVC). Below are just a few of the many benefits to this approach.
Let’s say you need to hit a specific endpoint from several different view models. You don’t want to be duplicating this network logic. By placing the networking logic in a Service, you can access these endpoint methods from a service instance in the view model.
Easier to write unit tests
It’s good practice to create your service as a protocol and then implement with a class or struct. By creating a protocol, it will be easier to create a mock of the service for unit testing purposes. You don’t want to be hitting actual REST APIs in your tests.
Separating your dependencies into their own types makes life a lot easier for new and current developers. By keeping all the framework/API logic in one file, a developer can quickly make sense of what’s being used in the project.
Let’s say your current app is using Firebase and you want to switch to Realm. All your storage provider logic will be centralized in one place allowing this large transition to go a little bit smoother. For example, both Firebase and MongoDB Realm have methods used to authenticate with their service. Having this functionality in one place will make the swap that much easier.
Sample Project Overview
*the code to the overview sections below have been shortened to reduce the length of the article. You can find the full length files on GitHub.
UserViewController will contain a
UITableView to display the retrieved users. I'm not using storyboards so the view controller is constructed programmatically.
- The view controller subscribes to the view model via Combine’s
ObservableObjectprotocol. The view controller will be notified when the
Userobjects are retrieved from the
fetchUsers method is calling
dataTask method on a background thread, we need to make sure we receive these updates on the main thread by calling the
As mentioned in the introduction, the sample project is using the MVVM architecture. The
UserViewController will hold an instance of our
UserViewControllerViewModel . We will use Combine's
ObservableObject protocol to subscribe to view model changes to update the view. The subscription is created in the
viewDidLoad method of the view controller.
- The service property used to access the
- The service property is set by passing in an instance of
JsonPlaceholderServicewhich conforms to the service protocol.
- The view model’s
retrieveUsersmethod access the service’s
fetchUsersmethod via the service property.
The sample project for this article will be hitting the
/user endpoint from the Jsonplaceholder website. This endpoint will return an array of ten distinct user JSON objects. This website also has a handful of other endpoints you can hit if you want to try and expand this project. The
JsonPlaceholderServiceProtocol requires just one method for conformance. The
fetchUsers method uses
URLSession's dataTask method to retrieve the json from the
- This will be the type of the service. The type is a protocol so it can be mocked (explained in the next section).
JsonPlaceholderServiceis the concrete implementation of the protocol. This will be used to hit the endpoint of the service and retrieve the
- The method used to retrieve the
Userobjects via the
In order to appropriately unit test the service, you will need to create a mock of it. This can be done in a handful of ways but I prefer the protocol approach. You can also create mocks via subclassing if you feel that’s easier to understand.
- This line is a little trick to get
.jsonfiles from your testing bundle. The
.jsonfile should contain a valid JSON object from the endpoint you are testing.
- Decodes the
.jsonfile into an array of
Userobjects simulating a successful network response.
UserViewControllerViewModelTests class has one test method to ensure the
fetchUsers method is correctly decoding and returning the JSON.
- A test method to check if the users array contains the same number of
Userobjects as the
.jsonfile. This method is called when the subject is initialized in the
Adding a service layer to your codebase has many benefits. Not only does it keep your codebase modular, you will also benefit from reusability, unit test coverage, readability, and replaceability.
I always feel it’s best to keep examples extremely slim and simple when explaining new concepts. The whole point of keeping your codebase modular is so you can easily expands its functionality.
The sample project for this article can be found on my GitHub.
Josh is an iOS Developer at Livefront where he is an ambassador to the southeast side of MN.