iOS

Swift 4 Generics: How to Apply Them in Your Code and iOS Apps


Question 1: Can I write one Swift function that can find the index/position of any specific instance of any type stored in any array that stores objects of that type?

Question 2: Can I write one Swift function that can determine if any specific instance of any type exists in any array that stores objects of that type?

When I say “any type,” I mean including custom types (like classes) that I define myself. NOTE: Yes, I know that I could use the Swift Array type’s built-in functions, index and contains, but I’ll be using simple example code today to illustrate some points about Swift generics.

In general, I’ll be covering generic programming, defined as:

… a style of computer programming in which algorithms are written in terms of types to-be-specified-later that are then instantiated when needed for specific types provided as parameters. This approach, pioneered by ML in 1973, permits writing common functions or types that differ only in the set of types on which they operate when used, thus reducing duplication.

Specifically, from the “Generics” topic in Apple’s Swift documentation:

Generic code enables you to write flexible, reusable functions and types that can work with any type, subject to requirements that you define. You can write code that avoids duplication and expresses its intent in a clear, abstracted manner.

Generics are one of the most powerful features of Swift, and much of the Swift standard library is built with generic code. … For example, Swift’s Array and Dictionary types are both generic collections. You can create an array that holds Int values, or an array that holds String values, or indeed an array for any other type that can be created in Swift. Similarly, you can create a dictionary to store values of any specified type, and there are no limitations on what that type can be. …

I’ve always been a strong proponent of code reuse, simplicity, and maintainability, and Swift’s generics, when used appropriately, go a long way in terms of helping me achieve these techniques I advocate. So the answer to Question 1 and Question 2 above is “yes.”

Living in a specific programming world

Let’s write a Swift function to tell us if a specific string exists in an array of strings:

Let’s test the function:

After creating the “existsManual” function for searching String arrays, suppose I decided I wanted similar functions for searching Integer, Float, and Double arrays — even for searching arrays of custom classes that I write? I’d end up spending precious time writing a lot of functions that all do the same thing. I’d have more code to support. Suppose I discovered a new/faster searching algorithm? Suppose I found a bug in my search algorithm? I’d have to change all versions of my search functions. Here’s the duplication hell in which I’d find myself:

The problem

In a world in which we’re so tied to types, where we’d have to create a new function for every typed array we want to search, we’d end up with lots of technical debt. Because of the incredible complexity of modern software, developers like you and I need to use the best practices, the best technologies, the best methodologies, and use our own neurons to the fullest to control this chaos. It was estimated that Windows 7 contained approximately 40 million lines of code while macOS 10.4 (Tiger) contained about 85 million lines. Estimating the number of possible behaviors which such systems can exhibit is computationally impossible.

Generics to the rescue

(Remember again that for the purpose of learning about generics, we’re still pretending that the Swift Array type’s built-in functions, index and contains, don’t exist.)

Let’s limit ourselves to the scope of trying to write one Swift function to tell us if a specific instance of one of Swift’s standard types exists in an array of the same Swift standard type, respectively, e.g., like String, Integer, Float, and Double. How?

Let’s turn to Swift generics, specifically, generic functions, type parameters, type constraints, and the Equatable protocol. Without defining any of these terms yet, I’ll write some code and let you think about what you see:

Let’s test my new generic function:

My new “exists” function is a generic function, one that “can work with any type.” Furthermore, when we look at the function’s signature,

we see that my “function uses a placeholder type name (called T, in this case) instead of an actual type name (such as Int, String, or Double). The placeholder type name doesn’t say anything about what T must be, but it does say that both [item] and [inArray] must be of the same type T, whatever T represents. The actual type to use in place of T is determined each time the [exists(_:_:)] function is called.”

The “exists” function’s placeholder type T is what’s called a type parameter, which:

“specify and name a placeholder type, and are written immediately after the function’s name, between a pair of matching angle brackets (such as <T>).

Once you specify a type parameter, you can use it to define the type of a function’s parameters (such as the [item] and [inArray] parameters of the [exists(_:_:)] function), or as the function’s return type, or as a type annotation within the body of the function. In each case, the type parameter is replaced with an actual type whenever the function is called.”

To reinforce what we’ve learned so far, here’s one Swift function that can find the index/position of any specific instance of any type stored in any array that stores objects of that type:

Let’s test it:

About Equatable

What’s this <T: Equatable> annotation on my “exists” function? It’s called a type constraint, and it specifies “that a type parameter must inherit from a specific class, or conform to a particular protocol or protocol composition.” I’m specifying that my “exists” function parameters, item:T and inArray:[T], must be of type T, and type T must conform to the Equatable protocol. Why?

All the Swift built-in types have been constructed to support the Equatable protocol. From the Apple docs: “Types that conform to the Equatable protocol can be compared for equality using the equal-to operator (==) or inequality using the not-equal-to operator (!=).” That’s why my generic function “exists” works with Swift types like String, Integer, Float, and Double. All these types define the == and != operators.

Custom types and generics

Suppose I create a new class called “BasicPerson” and define it as shown below. Can I use my “exists” function to find out if an instance of “BasicPerson” occurs in an array of that type? NO! And why? Review this code and then we’ll talk about it:

Look at the last line because it has this compiler error:

Swift generics

As if that’s not bad enough, you can’t use the Swift Array type’s built-in functions, index and contains on arrays of the “BasicPerson” type. (You’d have to define a closure each time you wanted to use those two methods and blah, blah, blah… I’m not even going there.)

So again, why?

Because the “BasicPerson” class doesn’t conform to the Equatable protocol (this is a hint, hint, 😉 for reading the rest of this article).

Conforming to Equatable

In order to allow my “BasicPerson” class to work with my “exists” and “find” generic functions, all’s I need to do is:

  • Mark the class as adopting the Equatable protocol; and,
  • Overload the == operator for class instances.

Note that “The standard library provides an implementation for the not-equal-to operator (!=) for any Equatable type, which calls the custom == function and negates its result.”

If you’re not familiar with operator overloading, I suggest you read up on the topic at these links here and here. Trust me, you want to understand operator overloading.

Note: I’m renaming the “BasicPerson” class to “Person” so they can co-exist in the same Swift playground. From here onwards, I’ll be referring to the “Person” class.

I’ll implement the == operator so that it compares the “name,” “weight,” and “sex” properties of one instance of the “Person” class with another. If two “Person” class instances have the same three properties, they’re equal. If any of the properties differ, they’re not equal (!=). Here’s how my “Person” class adopts the Equatable protocol:

Note the == overload above, which makes “Person” conform to the Equatable protocol. Note the arguments lhs and rhs in the == overload. It’s common, when operator overloading, to name the arguments to which the operator is being applied in regards to their physical positions in code, like so:

Will it work?!?!?

If you follow my directions, you’ll be able to create generic functions like my “exists” and “find” for use with any new types you create, like classes or structs. You’ll also be able to use Swift’s built-in functions, like index and contains, with Array type collections of your custom Equatable protocol-conforming classes and structs. It does work:

Further reading

Apple notes the benefits of the Equatable protocol — and more:

Adding Equatable conformance to your custom types means that you can use more convenient APIs when searching for particular instances in a collection. Equatable is also the base protocol for the Hashable and Comparable protocols, which allow more uses of your custom type, such as constructing sets or sorting the elements of a collection.

For example, if you adopt the Comparable protocol, you can overload and make use of the <, >, <=, and >= operators. Pretty cool.

Beware

Think about the “Person” class and a situation where we have instances like these:

Look at the last line because “Person” objects “Jan” and “Sue” are technically equal, even though they’re two different class instances. Your software is only as good as you design it. In database terminology, you’d need a “primary key” in a collection of “Person” classes — maybe add a variable for a GUID to the class design, or a social security number, or some other value you know would be guaranteed to be unique in a collection (array) of “Person” class instances. Or, you could use ===.

Enjoy!

iOS
Parse Migration Part 3: Setting up Cloud Code, Dashboard, and Push Notifications on Parse Server
iOS
Adding a Cloud Backend for Your iOS App Using Parse – Part 2
iOS
Working with App Thinning in iOS 9
Shares