When would you use an extension (or a category in Objective-C) instead of using a subclass and vice-versa?
Extensions can add new functionality to a type, but they cannot override existing functionality.
Extensions can add new computed properties, but they cannot add stored properties, or add property observers to existing properties.
With extensions, you can only add new methods and computed variables. However, you can achieve smoother integration into your code since the new functionality is available anywhere without having to add new classes to your codebase.
Subclassing can allow you to override methods of a superclass with custom implementations of methods, as well as adding additional functionality.
With subclassing, you can add new variables and you can override functions, but there is however a bigger footprint in your code and furthermore, you will need to add references to that specific subclass throughout your project wherever you make use of the added functionality.
In many cases, in real-world use cases, developers tend to use categories or extensions when an entire project needs (or could benefit from) a new behaviour, in other cases they will tend to use subclass.
Extensions and categories are most often used to add features like methods, initializers, computed properties and subscripts to an existing class without the need to create and reference a new subclass. This can prove particularly powerful when using extensions to add functionality to the built-in classes of the Swift or Objective-C language and the iOS SDK frameworks.
In other words, typically if we are adding general-purpose functionality that we want to be available to every instance of a given class we’ll use an extension or category in order to do this.
Conversely, if we want to create functionality that only applies to some particular instances of a given class, such as some of the buttons in our app which are used in particular circumstances but not others, then we would use sub-classing for that.
Let’s examine some typical use cases to get a better idea of how we typically use these approaches in real-world applications.
Subclassing Example
Use Case: A Custom UIButton
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
import Foundation import UIKit @IBDesignable class MyCustomButton: UIButton { // button corner radius @IBInspectable var cornerRadius: CGFloat = 0 { didSet { self.layer.cornerRadius = cornerRadius } } // button border color @IBInspectable var borderColor: UIColor = UIColor.clear { didSet { self.layer.borderColor = borderColor.cgColor } } // button border width @IBInspectable var borderWidth: CGFloat = 0 { didSet { self.layer.borderWidth = borderWidth } } } |
Extension Example
Use Case: Add functionality to String
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
// Example String extensions - Source: SwifterSwift public extension String { var charactersArray: [Character] { return Array(self) } var hasLetters: Bool { return rangeOfCharacter(from: .letters, options: .numeric, range: nil) != nil } static func loremIpsum(ofLength length: Int = 445) -> String { guard length > 0 else { return "" } // https://www.lipsum.com/ let loremIpsum = """ Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. """ if loremIpsum.count > length { return String(loremIpsum[loremIpsum.startIndex..<loremIpsum.index(loremIpsum.startIndex, offsetBy: length)]) } return loremIpsum } } |
Since extensions are global, at least within a module, functionality that is added using extensions will be available to any instance of a class and extension on that class.