Generics

Overview

Generics allow developers to write code which can work with any type. A common example is that of an Array. Consider the following code:

var myIntArray = Array<Int>() //same as [Int]()
myIntArray.append(1)
let firstIntElement = myIntArray.first //optional Int
When we call append() on the array, the parameter type must be an Int, similarly, the type of the first property is Int? (an optional Int). Note that we cannot add a String to our intArray:
myIntArray.append("one") //will error
and the following error will be displayed: Cannot convert value of type 'String' to expected argument type 'Int'

If we were to create an Array of Strings, the append() method's parameter must be a String, and the first property would have the type String>.

Avoiding duplication

Not only can we create Arrays of Ints and Strings, but we can also create arrays of our own custom types, for example Array<MyCustomClass>. In order for all of this to happen, the Array class makes use of Generics - this allows, for example, the type for the parameter of the append() method to match the type of item stored in the Array. We'll come back to Generic types in a bit.

Generic Methods

Lets say we want to write a method which will take two integers as parameters, and return one of the at random. It might look like this:

func random(a: Int, b: Int) -> Int{
    return Bool.random() ? a : b
}
If we then decided we wanted a method which would do the same but for Strings, it would look like this:
func random(a: String, b: String) -> String{
    return Bool.random() ? a : b
}
One solution to this problem would be to create a method where the type was Any however, this introduces two problems, firstly we would need to cast the result back to the type we want, and secondly, we could accidentally call it with two different types of values (in which case our cast would fail). The solution to this problem is to make the method generic. We create a method where we use a placeholder type name This is declared at the start of the method, so our solution might look like this:
func random<UnknownType>(a: UnknownType, b: UnknownType) -> UnknownType {
    return Bool.random() ? a : b
}
In the above example UnknownType is a name we have chosen to use to represent the type. It can be anything of our choosing, though it should be named using UpperCamelCase and by convention, a single letter is used, typically T (for Type), or E (for element - more common if you have a collectons of things, such as an Array. Therefore, by convention it's more likely we would write the above method as follows:
func random<T>(a: T, b: T) -> T {
    return Bool.random() ? a : b
}
The first use of T is it's declaration, indicated by the angular brackets and it's position immediately after the method name. Further uses of T are in place of where we would usually specify a concrete type. Note that the scope of T in this example is only within the method

Generic Types

Like Array, we can also create our own types that use generics. Quite often we might use these for collections of things, but to start of more simply, we will create a type which is a little like Swift's Optional type, but instead of having the potential to be nil, internally it will always have a non-nil value, but when we try and access it, it will randomly, be nil about half the time. We'll call our class Unreliable. The basic syntax to declare the class is as follows:

class Unreliable<T> { }
Note that we are naming our placeholder type T in this example, but we could just as easily name it Type

At this stage, if we wanted to create an intance of Unreliable, we would need to specify the type when we create it using angular brackets, e.g.

var unreliable = Unreliable<Int>()
If we do not, the compiler will complain that "Generic parameter T could not be inferred", i.e. it doesn't know what type to use. If however, the init method were to take a parameter of type 'T', then it would be able to do so. In this instance, we want to be able to construct our 'Unreliable' with an object, so we can create an init method as follows:
class Unreliable<T> {
    init(_ item: T) {
        
    }
}
We could now initialise it as follows:
var unreliable = Unreliable(4)
in which case T would be inferred as being of type Int. (It's also still fine to provide the type if you wish)

If we were to write the following however,

var unreliable = Unreliable("four")
T would be the type String

You may have realised at this point that we actually need to store the object in some form of intstance variable (and initialise it in the init method). This can be achieved as follows:

class Unreliable<T> {
    private var item : T
    init(_ item: T) {
        self.item = item
    }
}
Note how the type of the item variable is also of type T.

Finally, we can add a method to access our Unreliable as follows:

func get() -> T? {
    return Bool.random() ? self.item : nil
}
Again note how the return type is an Optional of type T.

To summarise, if we create an instance of Unreliable, T will be inferred from the type of the object passed into the init method. This has to match the type of the instance variable, so this is also declared as being of type T, and as the get() method has to return either something of type T or nill, so its return type is an optional T (T?)