Creating Custom Higher-Order Functions Can Help You Write More Abstract Swift Code

Improving your SDK by inventing new array transforming methods based on the standard foundation library

Maksym Teslia
Better Programming

--

image by author

Let’s imagine one of our app screens has a vanilla table view on it. The cell of this table view holds two labels: one reflects the username, and the second one reflects the model of the phone this user owns. Since separating the domain model from the view model is considered a good practice, we’ve created two entities to cover our needs. Here’s what they look like:

So, the User structure has all the information we need (yeah, in real life, it would have much more properties, I know), and the ViewModel structure has properties needed for cell construction. To build a table view, we would need an array of ViewModel objects.

The requirement from the client is to show the user only if they are older than 21. So, we need to filter users by age and then initialize a ViewModel from each remaining User. How would you manage such a case? Let me guess, you would create a separate function for filtering and transforming an array of users into an array of view models, which would look approximately like this (the approach might be different):

That looks nice and safe. You are passing an array of users with age as a predicate and can be sure no users younger than 21 are shown. If business needs related to this screen remain the same, this logic is perfect, but honestly, how often do business needs remain constant?

What if your client asks for filtering users not only by age but also by the phone model they have? Or what if the new requirement is to show only users who are younger than 30?

In our case, the amount of possible predicates is relatively small, so adding a few more parameters to the function could solve the issue. But imagine how messy will it be.

In order to keep functionality clean and beautiful, you could dedicate a separate function to each case and have a list of methods to handle each filtering option, but imagine how much repetitive code those methods will have.

The second way works fine if repetitive code is separated into one function, but what if we have more than one class where we need to use such a functionality? Would it be correct to create a list of functions in each class? No, it wouldn’t. Abstraction is a clue here.

A few words about abstraction:

“Abstraction is the process of generalisation by reducing the information content of a concept or an observable phenomenon, typically in order to retain only information which is relevant for a particular purpose. — New World Encyclopedia

So, in order to make functionality from the example above available for multiple classes, we need to make it abstract. To do that, we need to “reduce the information content” about the specific class and “retain only information which is relevant for a particular purpose”. The information in this case is a method realisation, so we need to create a function that is going to be “relevant for a particular purpose,” that is, will return an array of sorted and mapped elements.

If we want any class to be extended with some functionality related to the array, in Swift we do a kind of reversal wy and provide an array with this functionality.

In our case, we need to construct a view model from the user based on some predicate. So if the user does not meet some requirement, we skip them. This means we need to use a map() function in order to transform User to ViewModel, but how do we make a filtering? Writing a custom higher-order mapif function is going to help here:

So, now we have a generic method that can be fired up on any array in any possible context. It takes in two closures, runs them for every element of the array, and returns a transformed sequence. Here’s what that looks like:

  • condition closure of type ‘Element -> Bool’ takes in some element, compiles the given code with this element and returns a Boolean value
  • mappingAction closure of type ‘Element -> T’ runs its code with an input element and returns another value of generic type T.

The logic inside the function is the same as the logic of ‘getViewModel’ function of SomeClass, but all the types and entities are abstract.

The current use case of our ‘mapif’ function would be running it on [User] array to transform users to [ViewModel] for a table view data source:

From now on, if we need to make a filtering using more predicates, we can just modify a ‘condition’ closure with more parameters. Here’s what that looks like:

Writing abstract code can make your development process easier and faster because you won’t have to do the same operations repetitively. But it is always important to remember that in some cases abstraction might not be needed, and it is better to just keep functionality encapsulated inside of some class.

Basically, it is up to you to decide. The rule of thumb is that if you do the same operation in two different places and the logic of this operation has the same reason for potential change, it is better to make such logic abstract.

--

--