Back in the early 90's, when the primary language that I developed in was C, I had numerous custom libraries that contained functionality that was not part of the standard C library. I found these libraries extremely usefully because I tended to use the functionality they provided in most of my projects. This functionality included things such as converting the first letter of a string to uppercase or converting a double value to a currency string (two digits after the decimal point and a currency symbol). Having libraries such as this is extremely useful because there is always functionality that we find useful that is not included in the standard library of the language we are developing in. I usually implemented this extra functionality, in C, with global functions. In more modern object-oriented languages, we can implement this functionality by sub-classing the class we wish to add the functionality to, but in Swift we can use extensions to add this functionality to existing types without the need to use global functions or sub-classing. To make extensions even more useful, starting in Swift 2.0, Apple gave us the ability to extend protocols, which lets us add functionality to any type that adopts a protocol.
What we will learn in this chapter:
Extensions are one of the most useful features in the Swift language. They allow us to add functionality to an existing type even if we do not have the source code for the type. If you are not familiar with protocol extensions, you may be wondering how we can add functionality to a protocol when protocols do not contain any functionality. Well, that is arguably one of the most exciting features about extensions and we will see how to use protocol extensions later in the chapter. First, however, we will look at what extensions are and how to extend classes, structures, and enumerations.
With extensions, we can add the following items to an existing type:
One drawback of extensions is that we cannot override the functionality of the type we are extending. Extensions are designed to add additional functionality and are not designed as a means of changing the functionality of a type. Another thing that we cannot do with extensions is add stored properties; however, we can add computed properties.
To understand why extensions are so useful we need to understand the problem that they are designed to solve. In most object-oriented languages, when we want to add additional functionality to an existing class we generally sub-class the class we want to add the extra functionality to. We then add the new functionality to this new sub-class. The problem with this method is that we are not actually adding the functionality to the original class; therefore we have to change all instances of the original class, which need this extra functionality, to instances of this new sub-class. With some classes, such as the NSString class, it can take a significant amount of code to create a sub-class. There is a sample Objective-C project in the downloadable code that demonstrates this.
Another problem we can run into is that we can only subclass reference types (classes). This means we are unable to subclass value types such as a structure or enumeration. What makes matters even worse is the fact that the greater part of the Swift standard library is made up of value types. Therefore we are unable to add functionality to types from the Swift standard library by sub-classing them. Apple has also recommended that we prefer value types to reference types in our applications, which means that if we listen to Apple's recommendation we cannot subclass the majority of our custom types.
With extensions, we are able to add new functionality directly to the types that we are extending. This means that all instances of that type automatically receive the new functionality without the need to change the type of the instance. We are also able to extend both reference and value types, which includes protocols. As we will see later in this chapter, the ability to extend protocols is one of the things that make protocol-oriented programming possible.
Let's begin by looking at how we extend types such as structures, enumerations, and classes.
An extension is defined by using the extension
keyword followed by the name of the type you are extending. We then put the functionality that we are adding to the type between curly brackets. The following example shows how to define an extension:
extension String { // Add functionality here }
The previous example would add an extension to the String type from the Swift standard library. Since we can extend any type, we can use extensions to add functionality to types from the Swift standard library, types from frameworks/libraries, or our own custom types. While we can use extensions to add functionality to our own custom types, it is usually better to add the functionality directly to the type itself. The reason for this is that our code is easier to maintain if all of the functionality (code) for our custom types is located together.
If we are adding functionality to a framework and we have the code for that framework, it is still better to add the functionality with an extension rather than changing the code within the framework itself. The reason for this is that, if we add the functionality directly to the code within the framework, when we get newer versions of the framework our changes will be overwritten. Newer versions of the framework will not overwrite our extensions as long as we do not put them in a file that belongs to the framework.
To add the functionality to a type with an extension we use the same syntax that we use when we add the functionality to a standard Swift type. For example, the following code extends the String
type to add a method that returns an optional value that contains either the first character of the string or a nil
, if the string is empty:
extension String { func getFirstChar() -> Character? { guard self.characters.count > 0 else { return nil } return self[startIndex] } }
Once we add this extension to our application, all instances of the String
type can take advantage of the new functionality. There is also nothing special that needs to be done to access the functionality; instances of the String
type do not know or care whether the functionality came from the original implementation of the type or from an extension. The following example shows how we use the getFirstChar()
method:
var myString = "This is a test" print(myString.getFirstChar())
The previous example will print the character T to the console. It is just as easy to add other functionality such as subscripts to existing types. The following example shows how we would add a subscript to our String
extension that accepts a range operator and returns a substring with the characters defined by the range operator:
extension String { func getFirstChar() -> Character? { guard self.characters.count > 0 else { return nil } return self[startIndex] } subscript (r: Range<Int>) -> String { get { return substringWithRange(Range( start: startIndex.advancedBy(r.startIndex), end: startIndex.advancedBy(r.endIndex))) } } }
In Chapter 2, Our Type Choices, we mentioned that types that are normally implemented as primitives in other languages are implemented as named types in Swift. These include types that represent numbers, characters, and Boolean values. Since they are implemented as named types we are also able to extend them as we would any other type. As an example, if we wanted to extend the Int
type to add a method that would return the value of the integer squared, we could do it with an extension like this:
extension Int { func squared() -> Int { return self * self } }
We could then use this extension to get the value of any integer squared as shown in the next example:
var i = 21 print(i.squared())
Another example would be to extend the Double
type to add a method that would convert the value of the double to a String
type representing the value as a currency. This method would round the number to two decimal places and add a currency symbol. The following code demonstrates how we could do this:
extension Double { func currencyString() -> String { let divisor = pow(10.0, 2.0) let num = round(self * divisor) / divisor return "$\(num)" } }
We cannot add stored properties with extensions; however; we can add computed properties. Earlier in this section we added a method named squared()
to the Int
type. We could have implemented this functionality as a computed property, as shown in the following example:
extension Int { var squared: Int { return self * self } }
Now that we have seen how to extend a standard type such as classes, enumerations, or structures, let's see what protocol extensions are all about.