Simplest Networking Layer in iOS

Shahrukh Alam
Swift2Go
Published in
7 min readSep 28, 2021

This is a minimalistic approach to what we call the Networking Layer in iOS Development. We will try to look at it in the most logical & simplest way.

Code should be simple, not clever -Anonymous

Typical Networking Stack

Credits: Testing Tips & Tricks WWDC 2018/417

Typical Networking Layer

Typical Distributed Networking Layer Architecture

Retrospection

If you define the Problem correctly, you almost have the Solution -Steve Jobs

Let’s take a step back & think logically with some basic questions:

Did we think of anything simpler before writing those Protocols

You often hear this “Let’s start with a Protocol”. But, Why?

Swift encourages Protocol Oriented Programming, but that doesn’t mean only that. For instance, if you know Protein is good for your health, that doesn’t mean you won’t intake Carbs, Fats or Water for that matter.

Is it a real use case of Protocols

You often hear this “Protocols mean good code”. But, Why?

Protocols are great for polymorphic code, composition over inheritance (by Mattias Petter Johansson), abstraction & Testability. But, when we are dealing with value (not behavior), so abstraction or composition over inheritance is not needed/related. Then we should ask if we really have polymorphs, I mean do we have a case of Cats & Dogs or we are just creating a Pet from its head, body & tail.

Great watch for the right use case of Protocol: Beyond Crusty: Real-World Protocols by Rob Napier

Are the Protocols really adding a value

You often hear this “We might need it in the future”. But, who has seen the future?

Protocols for values encourage predicting the future much like Inheritance. For something really simple, we sometimes end up creating too many Generics with a higher barrier of entry & too many concrete types which would impact the compile time. Also starting with a Protocol puts you in a spiral trap of adding more of them. Few things to keep in mind about the Network Layer:

  • Is your team including junior devs know the in & outs of it?
  • Are they comfortable with the level of Generics in it?
  • Do they think it’s bulkier than it should be?

A great read for not to use Protocol: A case of premature abstractions by Tjeerd

Inspection

Typical Networking Layer is great for Distribution, Testability & Ease of Use. But, I think it lacks Readability or rather too much to read for something trivial like creating URLRequests.

Fundamental Types needed for Networking | Credits: matteomanferdini.com

Power of Extension

I think instead of creating 10s & 100s of concrete APIRequest types, we can achieve the same thing with simple extensions without even sacrificing Distribution, Testability & Ease of Use.

  1. Let’s first create the URL. We would use URLComponents as it offers a more flexible way for the same.

Now, the client can use this simple extension to create URLComponents as per the API requirements extending itself:

And just like that, we have our URL from URLComponents.

2. Next let’s add HTTPMethod, HTTPBody & HTTPHeaders. Again we use an extension on URLRequest along with builder pattern to modify the URLRequest struct using inout.

Do note we have marked the map function as private as we want clients to create their URLRequest only through the available builder APIs.

This is how it looks client side:

Till now, I believe we have not compromised on Distribution, Testability or Ease of Use although one might argue that we have to do 2 things for an API call: creating URComponent & building URLRequest. But, I think they are actually 2 different things.

On the brighter side, we didn’t end up creating 2 files for 2 API calls: UsersAPIRequest & UserDetailAPIRequest. So, we might not really need that APIRequest protocol at all from the Typical Network Layer.

There is a problem though if we have 100s of API calls, we can’t have all of them in a single extension of URLRequest. We can separate them based on Features (say URLRequest+Users.swift, URLRequest+Articles.swift etc) instead. How you define a Feature is completely up to you.

And just like that, we have our URLRequest.

3. Next let’s take the same approach for actual API client:

And this is how it looks client side:

It looks cool & works with so little code. But, here we have compromised few things:

  1. Distribution: On the client side we needed to specify the type of response by AnyPublisher<DetailResponse, Error> which was not the case with APIService from typical Networking Layer as its role was to do just that. Hence we also had to import Combine for Client which adds a Module Dependency, which goes against the Dependency Inversion of SOLID principle.
  2. Testing: As we are doing things in an extension of concrete URLSession, it’s difficult to mock it for Unit Testing. If you are the only developer or a small team in a startup, you might get away with it. But, the ROI of Testing in the form of saving time is huge.

Even the Truth should be tested once -Bangalore Proverb

We can partially solve problem (1) though using MetaType:

Client Side:

To solve problem (2), we might end up creating APIClient similar to typical Networking Layer injecting URLSession so that:

Both the above approaches are fine, but they will defeat our purpose of simplifying things. Just to be able to test, we shouldn’t add extra overhead to the production code. Is there a better way?

It turns out there is. We can get creative taking inspiration from Testing Tips & Tricks WWDC 2018/417 by mocking URLProtocol.

URLProtocol is another lower-level API that performs the underlying work of opening network connection, writing the request, and reading back a response. It is designed to be subclassed giving an extensibility point for the URL loading system.

Native URLSession Design | Credits: Testing Tips & Tricks WWDC 2018/417

This is how the mocking looks in code:

And now we can easily write the APIClient like test without changing any production code:

Simplification

We are actually done. Now, we have a fully (it depends though) functional Networking layer with just 3 simple extensions:

It’s actually so short & sweet that we might not have to extract it into a separate Dynamic Framework which would improve App Launch Time.

Justification

To measure improvements in Build Time & App Launch Time if any, I have created 2 Apps: the first one with Typical Network Layer (as a Dynamic Framework) with 10 APIRequests & second one with our (this) Simple Network Layer (as code) with the same 10 APIRequests. Here are the results on MacBook Air (M1, 2020, 16 GB) with iPhone SE (2nd Generation, iOS 15.0):

Note: There are few criticisms about extensions though w.r.t compilation times, readability & code clarity discussed in Benchmark of Swift extensions vs methods by Sasha Prokhorenko.

Conclusion

In this post, we discussed a minimalistic approach to Network Layer in iOS. You could check out the full playground implementation on Github. Obviously, as the project grows one would need more things, but the ideology remains the same.

Start simple, adapt, Evolve -SA

It highlights few points against the heavy or unnecessary use of Protocols from the very beginning. It also encourages leveraging the power of extensions. Don’t hesitate to extend the native types before creating your own type.

Although these things are debatable, it is making a clear point that we should write code with care. Even the most junior dev shouldn’t think twice before checking out the Network Layer. Please let me know what you think in the comments below.

Happy Coding & Sharing 😍
Cheers 🍺
Shahrukh Alam

--

--

Swift2Go
Swift2Go

Published in Swift2Go

a place where Swift Developers share knowledge.

Shahrukh Alam
Shahrukh Alam

Responses (1)