What are extensions?
Extensions allow us to modify data types to add new functionality to make them easier to use. Let’s create some basic extensions, starting off with an adding one extension.
Creating the addOne extension
Creating an extension is actually very easy. We will be adding this extension to the Integer data type.
extension Int {
func addOne() -> Int {
return self + 1
}
}
The extension tells us what data type we want to add this new function to. In this case, Int, or Integer. Then, we write our function into it. Fairly easy. Now, let’s call this.
var newInt = 10
newInt.addOne()
6.addOne()
If you see on the right, you will see that the outputs are 11 and 7. However, we have the issue that it doesn’t modify our original value. If we rewrite newInt after .addOne(), it is still 10. So, we need to make Swift add one back to itself.
Mutation
This is called mutation, where we “mutate” or modify the original value through the extension function. We cannot just write self += 1, we just need to modify a bit more than this.
extension Int {
mutating func addOne() {
self ++ 1
}
}
Now with this updated function, we can see if the original value updates.
var newInt = 8
newInt.addOne()
print(newInt)
As you can see, the original value now has 9! There are so many different ways to use mutation functions in extensions, and we are going to dive into another term called Protocol Extensions.
What are protocol extensions?
We just went over protocols. Contracts that define how their types must work. Protocol extensions allow us to define implementations inside the protocol and add functionality to all types that follow this protocol in one area.
Creating valueRange protocol extension
We will be making a valueRange protocol extension. We will feed in a value, and if it’s greater than the high point of the range, it will become that point. If it is lower than the low point, it will become the low point. If it is between, we will keep the current value.
extension Int {
func valueRange(lowPoint: Int, highPoint: Int) -> Int {
if (self > highPoint) {
return highPoint
} else if (self < lowPoint) {
return lowPoint
}
return self
}
}
let myInt: Int = 12
print(myInt.valueRange(lowPoint: 7, highPoint: 11))
The Data Types keep on coming!
When this is printed, the value will be 11. We had to make myInt a specified Integer. Swift has tons of data types we haven’t even gone over. There are tons of different Integers, all that do different things, such as UInt, Int8, Int64, and more. All of these have their strengths and weaknesses that make them easier to use in different instances.
If we use any of these other types, they will not work. Even though we regard the value as an Integer, the code will treat it as a different data type because it is more specific than we are. Luckily, there’s a fix.
BinaryInteger
Swift is able to fix this issue. We can use a new type, called BinaryInteger. This is a protocol that all of Swift’s integers follow. So if we rewrite the protocol extension, then it will work on tons of different data types.
Along with this, we will need to replace the parameters with a new value, Self. This method return basically tells the function that it will use whatever data type it was used with. So if we give it an Int, we will get an Int. If we give it an Int64, we will get back an Int64. You get the idea.
extension BinaryInteger {
func valueRange(lowPoint: Self, highPoint: Self) -> Self {
if (self > highPoint) {
return highPoint
} else if (self < lowPoint) {
return lowPoint
}
return self
}
}
let myNewInt: Int64 = 12
print(myNewInt.valueRange(lowPoint: 7, highPoint: 11))