Skip to content

A journey of thousand miles…

This week I spent some time adding a media gallery to the product I’m working on. You know, one of those things that mimic the native Photos user experience, where you can swipe to left and right to navigate to the next item, zoom in and out a photo… The usual suspects.

So, in order to move fast we decided to rely a third party library for it, instead of writing it from scratch.

We already had a well defined model object to represent a media item (photo or video). But the third party library that we chose requires its own model object, which makes total sense.

Also, it is possible we might end up switching to a different library in the nearly future, so it makes sense to have a clear boundary between our code and the third party library. I call it a boundary, some people call it a wrapper, some other people would call it an abstraction layer, but the point is that we don’t want to scatter references to this third party library across our codebase, we want those references to be isolated, encapsulated in just one class.

To the point

First, let’s assume our model object, the one in our codebase, looks like this:

The model object required by the third party library looks like this:

We could discuss if having a property to mark a MediaItem as an image or a video is an indication that MediaItem is the wrong abstraction, but that’s beyond the scope of this post (hint: I think it is the wrong abstraction, that any kind of type property in any class or structs suggests that there we are trying to model two different things with just on type)

Let’s say that boundary is a class Called MediaBrowser, that, first of all, transforms our model objects to instances of the model objects required by the third party library:

Now, that code might look quite straight forward, but in plain english, it will read like this:

For each instance of MediaItem, create an instance of GalleryItem, having in mind that, when creating an instance of GalleryItem, first, we need to check if MediaItem is a photo or a video. If it is a video, we need to pass to GalleryItem’s initialiser the value of the videoURL property in MediaItem, mark it as Video. However, it MediaItem is of type Image, we need to provide its imageURL to GalleryItem’s initialiser, as well as mark GalleryItem as Image.

It’s not rocket science. But describing what galleryData() code does requires a paragraph. And every single time you read this function, you need to translate it into that paragraph in order to understand what it is doing.

That happens because the code is quite imperative . There are a lot of specific, unnecessary details in there. Unnecessary because we don’t need to be that specific to describe what we expect the galleryData() function to do.

This is what we expect: we want to map each instance of MediaItem to a brand new instance of GalleryItem. Simple as that. However, the code, again, is telling a different, more complex, story, with plenty of details that we don’t need to be made aware of every time we read it.

The declarative solution

One of the things I like most about Swift is that it provides multiple ways to model abstractions, and multiple tools that can be used to write more declarative code.

For example, we can declare initialisers in extensions. So, we could do something like this, to extend the GalleryItem object (remember, this object is part of a third party library, and therefore out of our control):

And now, this would be our galleryData() function:

This function, now, clearly express my expectation: I want to create a new GalleryItem for each MediaItem. Period. I don’t care about how that happens, I don’t care about the details, I just trust GalleryItem to do the right thing and create an instance of itself correctly.

We have turned the previous, imperative code into declarative code.

Now, in plain English the galleryData() function reads:

For each instance of MediaItem, create an instance of GalleryItem.

Rinse and repeat

In the scope of this post, there is not a huge difference in terms of lines of code, or in terms of code complexity between both solutions. This, per se, is not going to turn complex code into simple code, buggy code into robust code.

But now, imagine applying the spirit of this approach, using the tools Swift provides to write declarative code, every single time there is an opportunity to do so. That would make a significant difference. Because a journey of a thousand miles begins with a single step.

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *