Generics are the means for writing useful and reusable code. They are also used to implement flexible data structures that are not constrained to a single data type.
Generics stand at the core of the Swift standard library. They are so deeply rooted in the language that you cannot and should not ignore them. In fact, after a while, you won’t even notice that you’re using generics.
Why do we need generics?
To illustrate the problem, let’s try to solve a simple question: create types that hold pairs of different values.
A naive attempt might look like this:
struct StringGroup { var first: String var second: String } struct IntGroup { var first: Int var second: Int } struct FloatGroup { var first: Float var second: Float }
Now, what if we need a type that holds combinations of Data instances? No problem, we’ll create a DataGroup structure!
struct DataGroup { var first: Data var second: Data }
And a type which holds different types, say a String and a Double? Here you go:
struct StringDoubleGroup { var first: String var second: Double }
We can now use our newly created types:
let group = StringGroup(first: "Uno", second: "Dos") print(group) // StringStringGroup(first: "Uno", second: “Dos") let numberGroup = IntPair(first: 1, second: 2) print(numberGroup) // IntGroup(first: 1, second: 2) let stringDoubleGroup = StringDoublePair(first: "Uno", second: 22.99) print(stringDoubleGroup) // StringDoubleGroup(first: "Uno", second: 22.99)
Is this the right way to go? No way! We must stop the type-explosion before it gets out of hand.
Generic Types
Wouldn’t it be cool to have just one type which can work with any value? How about this:
struct Group<T1, T2> { var first: T1 var second: T2 }
We arrived at a generic solution that can be used with any type.
Instead of providing the concrete-type of properties in the structure, we use placeholders: T1 and T2.
We can call our struct Group
because we don’t have to specify the supported types in the name of our structure.
Now we can write:
let floatFloatGroup = Group<Float, Float>(first: 2.5, second: 3.3) print(floatFloatGroup) // Group<Float, Float>(first: 2.50000023, second: 3.3)
We can even skip the placeholder types because the compiler is smart enough to figure out the type based on the arguments. (Type inference works by examining the provided values while compiling the code.)
let stringString = Group(first: "Hello", second: "World") print(stringString) // Group<String, String>(first: "Hello", second: "World") let stringDouble = Group(first: "It is for", second: 10.99) print(stringAndDouble) // Group<String, Double>(first: "It is for", second: 10.999999988999995) let intDate = Group(first: 12, second: Date()) print(intDate) // Group<Int, Date>(first: 12, second: 2019-05-15 11:22:10 +0000)
In Swift, we can define our generic classes, structures, or enumerations just as quickly.
Tune in for more.