Swift: Extensions
Like categories in Objective-C, extensions in Swift give you the opportunity to add functionality to a type. This is not only possible for your own types, but also for types for which you don’t have access to the source code.
Hint: This post is using Swift 3 and Xcode 8.3.3
How do extensions work?
So basically it’s very straightforward. Let’s look at an example:
extension Int { func calculateDouble() -> Int { return 2 * self } } var someInt = 3 print(someInt.calculateDouble()) // 6
In this example, we have created an extension for Int. On every Int value there is now another function available, that returns the doubled value.
It’s also possible to modify the type itself. If an extension’s function does that, it has to use the mutating keyword:
extension Int { mutating func double() { self = self * 2 } } var someInt = 3 someInt.double() print(someInt) //6
You can also create extensions for classes:
class TestClass { var i = 3 } extension TestClass { func demoFunction() { print("i is equal to \(i)") } var demoComputedValue: Int { get { return 10 } set { print("\(newValue)") } } } let test = TestClass() test.demoFunction() //i is equal to 3 print(test.demoComputedValue) //10 test.demoComputedValue = 20 //20
In this example we’ve declared a function and a computed property. It’s not possible to declare a stored property or override a function though.
Furthermore, an extension is allowed to confirm to a protocol. Then, the extension has to implement all the protocol’s functions, if the class doesn’t do so already:
class TestClass { var i = 3 } protocol TestProtocol { func protocolFunction() } extension TestClass: TestProtocol { func protocolFunction() { print("protocol function") } }
As you can see, defining extensions is very straightforward. Now let’s discuss when and how you should use them.
Using extensions for types for which you don’t have access to the source code
If you want to add functionality to a type for which you don’t have access to the source code, extensions are great! Of course there are other possibilities like subclassing or wrapping that type, but in these cases you have to use another type. By using extensions, you can use the original type and you are still able to add that functionality.
This applies to both types that are defined in Apple’s framework and types from third party frameworks.
Using extensions for your own types
So it’s very clear why you want to use extensions for types for which you don’t have access to the source code. Using it for your own code may sound strange in the first place though – after all you could add the behaviour directly to the type.
One reason why you still want to do this is because you are sharing code between several projects. In this case, it may be the cleaner approach to just add the app specific behaviour by using an extension.
Another reason for using extensions is readability and structure. By grouping functions and computed properties into extensions on its own, the actual type has much less code and it’s more clear what’s going on. So in a way, extensions can serve as pragma marks, but in a much more structured way.
A good example for this is using extensions for implementing an UITableViewDataSource.
UITableView example
View controllers tend to become very massive, and I’ve talked about this already in this post. In that post, I propose to create a class on its own, that act as an UITableViewDataSource – and I still like that approach:
import UIKit class TableViewController: UITableViewController { private let dataSource = DataSource() override func viewDidLoad() { dataSource.movies = ["Terminator","Back To The Future","The Dark Knight"] tableView.dataSource = dataSource } }
import UIKit class DataSource: NSObject, UITableViewDataSource { var movies = [String]() //MARK: - UITableViewDataSource func numberOfSections(in tableView: UITableView) -> Int { return 1 } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return movies.count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier")! cell.textLabel?.text = movies[indexPath.row] return cell } }
But there is also another possibility: You can just write an extension of your view controller and put all the UITableViewDataSource’s methods inside it. Even if you don’t move it to a file on its own – which is possible – your view controller’s code will be structured much better. Take a look at an example:
class TableViewController: UITableViewController { fileprivate var movies = [String]() override func viewDidLoad() { movies = ["Terminator","Back To The Future","The Dark Knight"] tableView.reloadData() } } extension TableViewController { override func numberOfSections(in tableView: UITableView) -> Int { return 1 } override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return movies.count } override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCell(withIdentifier: "cellIdentifier")! cell.textLabel?.text = movies[indexPath.row] return cell } }
So which approach is the better one – using an extension or a class on its own? It depends on what you want to achieve. If you want to use your data source for more than one table view, the reusability of the class approach is of course much better. In most cases that will not happen though – simply because different table views tend to be different in both their behaviour and appearance.
By using the class approach, this class can also provide the data. If you are using an extension, that’s not possible because extensions cannot add stored properties.
To make it short in a way, in most cases it’s absolutely fine to take the extensions approach when it comes to separating your UITableViewDataSource’s code from the corresponding view controller. And of course that holds true for other protocols as well.
Title image: @ maradon 333 / shutterstock.com