Understanding Swift protocol Associated types
Generics for protocols are what they are.
They enable us to write a common protocol that can accomodate variance in data types, for the same names, in the classes that conform to the protocol
(NOTE: THIS IS STATEMENT IS JUST MY ATTEMPT TO DEFINE associatedtype IN A SINGLE SENTENCE. DO NOT GET TURNED AWAY BY IT! :) ).
Say for example we want to create different structs to represent various types of numbers.
We want to represent a floating point number and an integer number type, and provide functionality in these types to make them print themselves:
struct IntegerNumber: Number {
var value: Int func printValue() {
print(“Integer: \(value)”)
}
}struct FloatingPointNumber: Number {
var value: Float func printValue() {
print(“Float: \(value)”)
}
}
Now since the functionality is common, we can abstract the printValue to a protocol and make our structs conform to it:
protocol Number {
func printValue()
}struct IntegerNumber: Number {
var value: Int func printValue() {
print(“Integer: \(value)”)
}
}struct FloatingPointNumber: Number {
var value: Float func printValue() {
print(“Float: \(value)”)
}
}
Now we can use a variable of type Number to hold instances of our structs, and call the Number methods on them:
let number: Number = FloatingPointNumber(value: 0.0) // or IntegerNumber(value: 0)number.printValue()
Using the number variable, we can only call the printValue() defined in the protocol.
Now suppose we want to access the underlying ‘value’ directly, so that we can print it ourselves, what shall we do?
We can move the commonly named ‘value’ to the protocol, but the types are different (IntegerNumber has value of type Int,
FloatingPointNumber has value of type Float)!
Enter Swift’s associatedtypes! 🙏
protocol Number {
associatedtype NumberType var value: NumberType { get } func printValue()
}
Now, for this protocol, the compiler looks for the following in the conforming classes or structs:
1) variable value (var value: Int, var value: Float, etc)
2) method printValue (the usual)
3) Explicit definition of ‘NumberType’.
OR
it tries to get the NumberType by looking at the type of instance variable ‘value’ .
We can make the types conform to the protocol in multiple ways:
// FORM 1struct IntegerNumber: Number {
var value: Int // compiler decides NumberType is Int func printValue() {
print(“Ingeger: \(value)”)
}
}// FORM 2
struct FloatingPointNumber: Number {
typealias NumberType = Float // explicit definitition var value: NumberType func printValue() {
print(“Float: \(value)”)
}
}
In FORM 1, we let let the compiler deduce the type of NumberType by looking at type of ‘value’ property.
In FORM 2, we explicitly TELL the compiler what ‘NumberType’ is.
Nice! Now we can …
// ERROR: Protocol ‘Number’ can only be used as a generic constraint because it has Self or associated type requirements
let number: Number = FloatingPointNumber(value: 0.0)
Error!
FIX: use ‘some’ keyword.
let number: some Number = FloatingPointNumber(value: 0.0) // or IntegerNumber(value: 0)print(number.value)
Thanks for reading!