Over the
past couple of years, I have been surprised with the number of senior level
developers that I have meet that do not use Generics. Not only do they not use generics but they
actually have very little understanding of them. In this article, I would like to explain why all
developers should not only learn generics but use them regularly. While this article focuses on the Swift
programming language, the concepts of why you should learn and use generics will
apply to almost any language that have generics similar to Swift.
I will
start off by demonstrating the problem that generics solve. Let’s say we are developing a new application
and within this application we have the need to swap the value of two integer
variables. To meet this need, in Swift, we
could very easily write a function like this:
func swapInt(_ a: inout Int, _ b: inout Int) {
let tmp = a
a = b
b = tmp
}
This
function could then be used like this:
var one = 1
var two = 2
swapInt(&one,
&two)
Now, a
few days or weeks later, as we continue to write the application, we discover a
need to swap the values of two string variables. We could then add another function to do this. The following code shows this new function:
func swapInt(_ a: inout String, _ b: inout String) {
let tmp = a
a = b
b = tmp
}
As time
goes by we may end up with several functions like the previous swap functions but with different
parameter types. Instead of having all
of these different functions, in the beginning we could have created one generic swap function
that looked like this:'
func
swapGeneric<T>(_ a: inout T, _ b: inout T) {
let tmp = a
a = b
b = tmp
}
Notice
that the generic version of the swap function uses a placeholder, capital T,
rather than the actual parameter type. This
placeholder tells the compiler that we will define the type to use at runtime. Since the T placeholder is used for both
parameters, they are required to be of the same type.
We would
use this generic function exactly like we would use the non-generic swap functions. The following code illustrates this:
var one = 1
var two = 2
swapGeneric(&one,
&two)
Now the
parameters can be instances of any type as long as they are instances of the same type. We could swap the values of two string types
like this:
var one =
“one”
var two =
“two”
swapGeneric(&one,
&two)
Swapping
values isn’t too exciting so let’s look at something that does a bit more by
seeing how we could use generics to create a very basic queue type. The following code shows how to do this.
struct
Queue<T> {
private var items = [T]()
public mutating func push(_ item: T) {
items.append(item)
}
public mutating func pop() -> T? {
if (items.count > 0) {
return
items.remove(at: 0)
} else {
return nil
}
}
}
We would use this queue type like this:
var queue =
Queue<Int>()
queue.push(1)
queue.push(2)
print(queue.pop())
print(queue.pop())
One of the best things about this queue type is
it is ready to accept any type. We could
very easily create an instance of the Queue type that would store String values
like this:
var queue =
Queue<String>()
One of the questions that someone new to generics
may ask is: Why don’t we just use the Any type rather than generics? In Swift, the Any type allows us to use instances
of any type. The following code shows
how we could create a Queue type that used the Any type:
struct
QueueAny {
private var items = [Any]()
public mutating func push(_ item: Any) {
items.append(item)
}
public mutating func pop() -> Any? {
if (items.count > 0) {
return items.remove(at: 0)
} else {
return nil
}
}
}
The QueueAny type lets us create a queue that
can contain any type similar to the previous generic queue however there are
several disadvantages to this method.
The one disadvantage that we will focus on here is the ability to add
any type to the queue. For example this
code would be perfect ok with the QueueAny queue:
var queueAny
= QueueAny()
queueAny.push(2)
queueAny.push("Hi")
Notice how we are able to add both an integer
and a string to the same queue. The
QueueAny type functions similar to how Arrays worked in Objective-C. Now if we popped an item off the queue we
would need to typecast it before we used any of the methods or properties of
the type. This does not allow us to have any
compile time checks to make sure we are using the correct types in our queue
and is prone to error, as any Objective-C developer can tell you.
With the generic Queue type, since we
explicitly define the type of items stored in the queue, we are able to use the
properties and methods of that type without the need to typecast. This gives us the compile time checks that
ensures we are using the correct types.
The answer to the question about why you should
learn generics is: Generics enable us to
write very flexible and reusable code that is also very safe. In this article we only scratched the surface
of generics. If you are unfamiliar with
generics I would recommend taking the time to learn more about them.
In my Mastering Swift 4 book there is a chapter dedicated to generics
that gives the reader a good introduction to generics which shows how to create
generic functions, types and protocols.
We also look at using type constraints with generics and the new generic
subscripting feature that was introduced in Swift 4.
In my Swift 4 Protocol Oriented Programmingbook we also have a chapter dedicated to
generics. In that chapter, we briefly
cover generic types, protocols, type constraints and generic subscripting. We also include more advance topics like how
generics are used in the Swift standard library and how to use generics to
implement the Copy-on-Write feature for custom value types.